diff --git a/packages/react-app/src/App.less b/packages/react-app/src/App.less index eccaf83e..133612ca 100644 --- a/packages/react-app/src/App.less +++ b/packages/react-app/src/App.less @@ -257,6 +257,23 @@ select:focus { } } +/* Parcel Info */ +.parcel-info { + border-left: 1px solid @gray-4; + padding: 1rem; +} + +/* Header */ +.search-and-connect-wrapper { + display: flex; + flex-direction: row; + justify-content: space-between; + + @media (max-width: 1024px) { + justify-content: initial; + } +} + /* BrowsePlots Layout */ .progress-bar-indicator { margin-left: 3rem; @@ -291,49 +308,47 @@ select:focus { height: 100%; display: grid; grid-template-columns: 400px 1fr; - grid-template-rows: 3rem 4rem 1fr; + grid-template-rows: 4rem 230px 1fr; grid-column-gap: 0px; grid-row-gap: 0px; @media (max-width: 1024px) { grid-template-columns: 1fr; - grid-template-rows: 3rem repeat(2, 4rem) 300px 4rem 5fr; + grid-template-rows: 0 repeat(2, 4rem) 300px 4rem 5fr; } // Responsive for mobile landscape screens @media (max-height: 500px) { - grid-template-rows: 3rem repeat(2, 4rem) 300px 4rem 600px; + grid-template-rows: 0 repeat(2, 4rem) 300px 4rem 600px; } } -.progress-bar { - grid-area: 1 / 1 / 2 / 3; -} .logo-link { - grid-area: 2 / 1 / 3 / 2; + grid-area: 1 / 1 / 2 / 2; } .connect-wallet-section { - grid-area: 2 / 2 / 3 / 3; + grid-area: 1 / 2 / 2 / 3; border-color: @gray-4; - justify-content: flex-end; + justify-content: space-between; +} +.parcel-info { + grid-area: 2 / 2 / 3 / 3; } .plot-detail { - grid-area: 3 / 1 / 4 / 2; + grid-area: 2 / 1 / 4 / 2; } .plot-tabs { - grid-area: 3 / 1 / 4 / 2; + grid-area: 2 / 1 / 4 / 2; width: 100%; } .plot-map { grid-area: 3 / 2 / 4 / 3; + border-left: 1px solid @gray-4; } @media (max-width: 1024px) { - .progress-bar { - grid-area: 1 / 1 / 2 / 2; - } .logo-link { - grid-area: 2 / 1 / 3 / 2; + grid-area: 1 / 1 / 2 / 2; } .connect-wallet-section { grid-area: 3 / 1 / 4 / 2; @@ -347,7 +362,13 @@ select:focus { grid-area: 5 / 1 / 7 / 2; max-width: 100vw; } + .parcel-info { + display: none; + } .plot-detail { grid-area: 6 / 1 / 7 / 2; } + .sensor-header-info { + display: none; + } } diff --git a/packages/react-app/src/assets/images/icon-chart.png b/packages/react-app/src/assets/images/icon-chart.png new file mode 100644 index 00000000..2ff98f13 Binary files /dev/null and b/packages/react-app/src/assets/images/icon-chart.png differ diff --git a/packages/react-app/src/assets/images/icon-globe.svg b/packages/react-app/src/assets/images/icon-globe.svg new file mode 100644 index 00000000..038b46f9 --- /dev/null +++ b/packages/react-app/src/assets/images/icon-globe.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/react-app/src/components/Header.tsx b/packages/react-app/src/components/Header.tsx index 6d5414b5..fc0bf4f3 100644 --- a/packages/react-app/src/components/Header.tsx +++ b/packages/react-app/src/components/Header.tsx @@ -1,14 +1,16 @@ import React from "react"; -import { ConnectWalletButton, SearchPlots } from "."; - +import { ConnectWalletButton, SearchPlots, SensorHeaderInfo } from "."; interface Props { connectWallet: () => void; } -export default function Header({ connectWallet }: Props) { +export default function Header({ connectWallet }: Props): JSX.Element { return (
- - + +
+ + +
); } diff --git a/packages/react-app/src/components/ParcelInfo.tsx b/packages/react-app/src/components/ParcelInfo.tsx new file mode 100644 index 00000000..8dbcac81 --- /dev/null +++ b/packages/react-app/src/components/ParcelInfo.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { timesAgo, tsToDate } from "../utils"; +import IconChart from "../assets/images/icon-chart.png"; +import IconGlobe from "../assets/images/icon-globe.svg"; +interface SensorData { + snr: number; + vbat: number; + latitude: number; + longitude: number; + gas_resistance: number; + temperature: number; + pressure: number; + humidity: number; + light: number; + temperature2: number; + gyroscope: [number, number, number]; + accelerometer: [number, number, number]; + timestamp: string; + random: string; +} +interface Props { + sensorData: SensorData; +} +interface DataBoxProps { + value: string; + label: string; + unit?: string; +} +function DataBox({ value, label, unit }: DataBoxProps): JSX.Element { + return ( +
+
+ {value} +
+ {label} +
{unit}
+
+
+ View History +
+ ); +} +export default function ParcelInfo({ sensorData }: Props): JSX.Element { + const { temperature, pressure, humidity, gas_resistance, timestamp, latitude, longitude } = sensorData; + const localisedTime = new Date().toLocaleTimeString("en-US", { + timeZone: "America/Denver", + hour: "2-digit", + minute: "2-digit", + }); + const fetchedTime = tsToDate(timestamp); + + return ( +
+
+ Globe +
+ {latitude}, {longitude} +
+ BENNET CREEK ROAD, POWELL WYOMING 82435 +
+
+
It’s currently {localisedTime}
[-7 GMT] -------------- Last transmissoin + ~{timesAgo(fetchedTime)} ago{" "} + + view on MachineFi + +
+ + + + +
+
+ ); +} diff --git a/packages/react-app/src/components/PlotList.tsx b/packages/react-app/src/components/PlotList.tsx index 4e04729c..1ebb960c 100644 --- a/packages/react-app/src/components/PlotList.tsx +++ b/packages/react-app/src/components/PlotList.tsx @@ -27,7 +27,7 @@ export default function PlotList({ plots, emptyMessage }: Props) { }; return ( -
+
{displayedPlots.map((plot: Plot, idx: number) => ( {/* modulo delay by 10 because plot buttons are added in batches of 10 */} diff --git a/packages/react-app/src/components/ProgressBar.tsx b/packages/react-app/src/components/ProgressBar.tsx deleted file mode 100644 index c6179809..00000000 --- a/packages/react-app/src/components/ProgressBar.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import { useAppSelector } from "../hooks"; - -export default function ProgressBar() { - const plots = useAppSelector(state => state.plots.plots); - const percent_sold = Math.ceil((plots.filter(plot => plot.sold).length / plots.length) * 100); - return ( -
- {plots.length > 0 && (percent_sold < 100 ? "Parcel 0 sale is live!" : "Parcel 0 sale is sold out!")} - {plots.length === 0 && "Fetching plots..."} - {percent_sold < 100 ? ( -
-
-
-
- {percent_sold > 0 ? `${percent_sold}% already sold!` : "Be the first owner!"} -
- ) : null} -
- ); -} diff --git a/packages/react-app/src/components/SensorHeaderInfo.tsx b/packages/react-app/src/components/SensorHeaderInfo.tsx new file mode 100644 index 00000000..d5b6a2ce --- /dev/null +++ b/packages/react-app/src/components/SensorHeaderInfo.tsx @@ -0,0 +1,7 @@ +import * as React from "react"; + +const SensorHeaderInfo = () => { + return
Parcel 0
; +}; + +export default SensorHeaderInfo; diff --git a/packages/react-app/src/components/index.ts b/packages/react-app/src/components/index.ts index 96edb57c..e6432368 100644 --- a/packages/react-app/src/components/index.ts +++ b/packages/react-app/src/components/index.ts @@ -4,7 +4,6 @@ export { default as PlotMap } from "./PlotMap"; export { default as PlotButton } from "./PlotButton"; export { default as PlotDetail } from "./PlotDetail"; export { default as PlotList } from "./PlotList"; -export { default as ProgressBar } from "./ProgressBar"; export { default as BuyPlot } from "./BuyPlot"; export { default as ViewPlot } from "./ViewPlot"; export { default as PlotTabs } from "./PlotTabs"; @@ -12,3 +11,5 @@ export { default as LogoDisplay } from "./LogoDisplay"; export { default as Header } from "./Header"; export { default as ConnectWalletButton } from "./ConnectWalletButton"; export { default as SearchPlots } from "./SearchPlots"; +export { default as ParcelInfo } from "./ParcelInfo"; +export { default as SensorHeaderInfo } from "./SensorHeaderInfo"; diff --git a/packages/react-app/src/containers/ParcelInfoContainer.tsx b/packages/react-app/src/containers/ParcelInfoContainer.tsx new file mode 100644 index 00000000..0facf2ec --- /dev/null +++ b/packages/react-app/src/containers/ParcelInfoContainer.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { gql, useQuery, InMemoryCache, ApolloClient } from "@apollo/client"; +import { ParcelInfo } from "../components"; + +const PEBBLE_DEVICE_ID = "351358813276802"; +const pebbleUri = "https://pebble.iotex.me/v1/graphql"; + +const pebbleClient = new ApolloClient({ + uri: pebbleUri, + cache: new InMemoryCache(), +}); + +const GET_SENSOR_DATA = gql` + query getDevices($deviceId: String!) { + pebble_device_record( + limit: 1 + order_by: { timestamp: desc } + where: { imei: { _eq: $deviceId }, latitude: { _neq: "200.0000000" } } + ) { + temperature + humidity + gas_resistance + pressure + latitude + longitude + timestamp + } + } +`; + +function ParcelInfoContainer(): JSX.Element { + const { loading, error, data } = useQuery(GET_SENSOR_DATA, { + variables: { deviceId: PEBBLE_DEVICE_ID }, + client: pebbleClient, + }); + if (loading) return

Sensor Data Loading ...

; + if (error) console.log(error); + if (error) return

Sensor Data Load Failed.

; + console.log(data?.pebble_device_record[0]); + return ( +
+ +
+ ); +} + +export default ParcelInfoContainer; diff --git a/packages/react-app/src/containers/index.ts b/packages/react-app/src/containers/index.ts new file mode 100644 index 00000000..6cd0c774 --- /dev/null +++ b/packages/react-app/src/containers/index.ts @@ -0,0 +1 @@ +export { default as ParcelInfoContainer } from "./ParcelInfoContainer" \ No newline at end of file diff --git a/packages/react-app/src/utils.ts b/packages/react-app/src/utils.ts new file mode 100644 index 00000000..27ae6c0c --- /dev/null +++ b/packages/react-app/src/utils.ts @@ -0,0 +1,38 @@ +export function tsToDate(ts: string): Date { + return new Date(Number(ts) * 1000); +} + +export function timesAgo(date: Date): string { // source: https://stackoverflow.com/a/55999110/458679 + const seconds = Math.floor((+new Date() - +date) / 1000); // get the diffrence of date object sent with current date time of the system time + let interval = Math.floor(seconds / 31536000); // divide seconds by seconds in avg for a year to get years + + //conditioning based on years derived above + if (interval > 1) { + return interval + " years"; + } + interval = Math.floor(seconds / 2592000); // months check similar to years + if (interval > 1) { + return interval + " months"; + } + interval = Math.floor(seconds / 86400); // days check similar to above + if (interval > 1) { + return interval + " days"; + } + interval = Math.floor(seconds / 3600); // hours check + if (interval > 1) { + return interval + " hours"; + } + interval = Math.floor(seconds / 60); // minutes check + if (interval > 1) { + return interval + " min"; + } + return Math.floor(seconds) + " seconds"; // seconds check at the end +} + +// withYears 22 years +// withMonths 9 months +// withDays 5 days +// withPreviousDay 24 hours +// withHours 2 hours +// withMinutes 5 min + diff --git a/packages/react-app/src/views/BrowsePlots.tsx b/packages/react-app/src/views/BrowsePlots.tsx index eb0c11f1..e59d9e39 100644 --- a/packages/react-app/src/views/BrowsePlots.tsx +++ b/packages/react-app/src/views/BrowsePlots.tsx @@ -4,7 +4,7 @@ import { Link } from "react-router-dom"; import { toast } from "react-toastify"; import { useContractLoader, useAppSelector, useAppDispatch, useUserSigner } from "../hooks"; -import { PlotMap, ProgressBar, PlotDetail, LogoDisplay, Header } from "../components"; +import { PlotMap, PlotDetail, LogoDisplay, Header } from "../components"; import { setPlots } from "../actions"; import { PlotTabs } from "../components"; import { Plot } from "../models/Plot"; @@ -13,6 +13,7 @@ import { fetchedPlots, setCommunalLand, setParcelGeojson } from "../actions/plot import { fetchMetadata } from "../data"; import updatePlots from "../helpers/UpdatePlots"; import { setWhitelistedAmount } from "../actions/userSlice"; +import { ParcelInfoContainer } from "../containers"; interface Props { networkProvider: any; @@ -127,7 +128,6 @@ export default function BrowsePlots({ networkProvider, web3Modal }: Props) { return (
- @@ -137,6 +137,7 @@ export default function BrowsePlots({ networkProvider, web3Modal }: Props) { )}
+ {/* key prop is to cause rerendering whenever it changes */}