Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add parcel list modal #398

Merged
merged 9 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import * as turf from "@turf/turf";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import type { InvocationConfig } from "@web3-storage/upload-client";
import ParcelList from "./parcels/ParcelList";

export const ZOOM_GRID_LEVEL = 17;
const GRID_DIM_LAT = 140;
Expand Down Expand Up @@ -170,8 +171,8 @@ export type MapProps = {
paymentToken: NativeAssetSuperToken;
sfFramework: Framework;
setPortfolioNeedActionCount: React.Dispatch<React.SetStateAction<number>>;
portfolioParcelCenter: Point | null;
setPortfolioParcelCenter: React.Dispatch<React.SetStateAction<Point | null>>;
parcelNavigationCenter: Point | null;
setParcelNavigationCenter: React.Dispatch<React.SetStateAction<Point | null>>;
isPortfolioToUpdate: boolean;
auctionStart: BigNumber;
auctionEnd: BigNumber;
Expand Down Expand Up @@ -199,7 +200,7 @@ function Map(props: MapProps) {
setSelectedParcelId,
interactionState,
setInteractionState,
portfolioParcelCenter,
parcelNavigationCenter,
} = props;
const { data, fetchMore, refetch } = useQuery<PolygonQuery>(query, {
variables: {
Expand Down Expand Up @@ -260,11 +261,12 @@ function Map(props: MapProps) {
const [interactiveLayerIds, setInteractiveLayerIds] = React.useState<
string[]
>([]);
const [invalidLicenseId, setInvalidLicenseId] = useState("");
const [invalidLicenseId, setInvalidLicenseId] = React.useState("");
const [newParcel, setNewParcel] = React.useState<{
id: string;
timerId: number | null;
}>({ id: "", timerId: null });
const [showParcelList, setShowParcelList] = React.useState<boolean>(false);

const router = useRouter();

Expand Down Expand Up @@ -751,10 +753,10 @@ function Map(props: MapProps) {
}, [data]);

useEffect(() => {
if (portfolioParcelCenter) {
if (parcelNavigationCenter) {
flyToLocation({
longitude: portfolioParcelCenter.coordinates[0],
latitude: portfolioParcelCenter.coordinates[1],
longitude: parcelNavigationCenter.coordinates[0],
latitude: parcelNavigationCenter.coordinates[1],
zoom: ZOOM_GRID_LEVEL,
duration: 500,
padding: {
Expand All @@ -764,11 +766,11 @@ function Map(props: MapProps) {
});

setSelectedParcelCoords({
x: portfolioParcelCenter.coordinates[0],
y: portfolioParcelCenter.coordinates[1],
x: parcelNavigationCenter.coordinates[0],
y: parcelNavigationCenter.coordinates[1],
});
}
}, [portfolioParcelCenter]);
}, [parcelNavigationCenter]);

return (
<>
Expand Down Expand Up @@ -915,6 +917,17 @@ function Map(props: MapProps) {
<Image src={"satellite_ic.png"} width={30} />
</Button>
</ButtonGroup>
<Button
className="bg-purple p-0 border-0 parcel-list-btn"
onClick={() => setShowParcelList(true)}
>
<Image src="list.svg" alt="parcel list" width={38} />
</Button>
<ParcelList
showParcelList={showParcelList}
handleCloseModal={() => setShowParcelList(false)}
{...props}
/>
</>
);
}
Expand Down
2 changes: 1 addition & 1 deletion components/cards/StreamingInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type StreamingInfoProps = {
setSelectedParcelId: React.Dispatch<React.SetStateAction<string>>;
setInteractionState: React.Dispatch<React.SetStateAction<STATE>>;
setPortfolioNeedActionCount: React.Dispatch<React.SetStateAction<number>>;
setPortfolioParcelCenter: React.Dispatch<React.SetStateAction<Point | null>>;
setParcelNavigationCenter: React.Dispatch<React.SetStateAction<Point | null>>;
isPortfolioToUpdate: boolean;
setIsPortfolioToUpdate: React.Dispatch<React.SetStateAction<boolean>>;
};
Expand Down
209 changes: 209 additions & 0 deletions components/parcels/Foreclosure.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { useState, useEffect } from "react";
import { BigNumber } from "ethers";
import { gql, useQuery } from "@apollo/client";
import { Container } from "react-bootstrap";
import { Framework } from "@superfluid-finance/sdk-core";
import type { Point } from "@turf/turf";
import * as turf from "@turf/turf";
import { GeoWebContent } from "@geo-web/content";
import { Contracts } from "@geo-web/sdk/dist/contract/types";
import { PCOLicenseDiamondFactory } from "@geo-web/sdk/dist/contract/index";
import { MAX_LIST_SIZE, Parcel, ParcelsQuery } from "./ParcelList";
import ParcelTable from "./ParcelTable";
import { STATE } from "../Map";
import { getParcelContent } from "../../lib/utils";

interface ForeclosureProps {
sfFramework: Framework;
geoWebContent: GeoWebContent;
registryContract: Contracts["registryDiamondContract"];
setSelectedParcelId: React.Dispatch<React.SetStateAction<string>>;
setInteractionState: React.Dispatch<React.SetStateAction<STATE>>;
handleCloseModal: () => void;
setParcelNavigationCenter: React.Dispatch<React.SetStateAction<Point | null>>;
shouldRefetch: boolean;
setShouldRefetch: React.Dispatch<React.SetStateAction<boolean>>;
}

type Bid = Parcel & {
timestamp: number;
licenseOwner: string;
};

const foreclosureQuery = gql`
query Foreclosure($skip: Int) {
geoWebParcels(
first: 1000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to get 1000 parcels here? Or can we limit it to MAX_LIST_SIZE?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in NeedsTransfer querying for only 25 parcels wouldn't result in 25 parcels foreclosed or that needs transfer because we don't have a way to query the subgraph directly for that, but e.g. need to call isPayerBidActive on each to tell if a parcel is foreclosed.

So we just get a pool of parcels to check and loop over them, performance should be alright even when there are 1000 parcels because it is done asynchronously.
A way to reduce the number of calls to the Ethereum node is to use ethers JsonRpcBatchProvider, in this comment the author was wondering if 1 million calls is too much so 1000 shouldn't be a problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great find on the JsonRpcBatchProvider. We should try to limit RPC calls as much as possible. Looks like this is working well with Infura.

I also see Ceramic multiqueries working well and also the Ceramic caching.

This does now result in many IPFS lookups via our delegate node. This should be fine, as they are only calls for content routing and do not return or fetch actual content. However, there are many 404s to /block/stat. I will look into how to remove these calls, as they are unnecessary. findprovs is what is needed.

orderBy: currentBid__timestamp
orderDirection: desc
skip: $skip
) {
id
createdAtBlock
bboxN
bboxS
bboxE
bboxW
licenseOwner
licenseDiamond
currentBid {
forSalePrice
contributionRate
timestamp
}
}
}
`;

function Foreclosure(props: ForeclosureProps) {
const {
sfFramework,
geoWebContent,
registryContract,
setSelectedParcelId,
setInteractionState,
handleCloseModal,
setParcelNavigationCenter,
shouldRefetch,
setShouldRefetch,
} = props;

const [parcels, setParcels] = useState<Bid[] | null>(null);

const { data, refetch, networkStatus } = useQuery<ParcelsQuery>(
foreclosureQuery,
{
variables: {
skip: 0 * MAX_LIST_SIZE,
},
notifyOnNetworkStatusChange: true,
}
);

useEffect(() => {
if (!data) {
return;
}

if (data.geoWebParcels.length === 0) {
setParcels([]);
return;
}

let isMounted = true;

(async () => {
const _parcels: Bid[] = [];
const promises = [];

for (const parcel of data.geoWebParcels) {
const licenseDiamondAddress = parcel.licenseDiamond;
const licenseDiamondContract = PCOLicenseDiamondFactory.connect(
licenseDiamondAddress,
sfFramework.settings.provider
);

const promiseChain = (async () => {
if (_parcels.length === MAX_LIST_SIZE) {
return;
}

const isPayerBidActive =
await licenseDiamondContract.isPayerBidActive();

if (!isPayerBidActive) {
const parcelId = parcel.id;
const createdAtBlock = parcel.createdAtBlock;
const currentBid = parcel.currentBid;
const forSalePrice = BigNumber.from(currentBid.forSalePrice);
const timestamp = currentBid.timestamp;
const poly = turf.bboxPolygon([
parcel.bboxW,
parcel.bboxS,
parcel.bboxE,
parcel.bboxN,
]);
const center = turf.center(poly);
const parcelContent = await getParcelContent(
registryContract.address.toLowerCase(),
geoWebContent,
parcel.id,
parcel.licenseOwner
);
const name =
parcelContent && parcelContent.name
? parcelContent.name
: `Parcel ${parcel.id}`;

_parcels.push({
parcelId: parcelId,
status: "In Foreclosure",
createdAtBlock: createdAtBlock,
name: name,
timestamp: timestamp,
price: forSalePrice,
center: center.geometry,
licenseOwner: parcel.licenseOwner,
});
}
})();
promises.push(promiseChain);
}

await Promise.allSettled(promises);

if (isMounted) {
const sorted = sortParcels(_parcels);

setParcels(sorted);
}
})();

return () => {
isMounted = false;
};
}, [data]);

useEffect(() => {
if (!shouldRefetch) {
return;
}

refetch({
skip: 0,
});

setShouldRefetch(false);
}, [shouldRefetch]);

const sortParcels = (parcels: Bid[]): Bid[] => {
const sorted = [...parcels].sort((a, b) => {
let result = 0;

result = a.timestamp > b.timestamp ? -1 : 1;

return result;
});

return sorted;
};

const handleAction = (parcel: Parcel): void => {
handleCloseModal();
setInteractionState(STATE.PARCEL_SELECTED);
setSelectedParcelId(parcel.parcelId);
setParcelNavigationCenter(parcel.center);
};

return (
<Container>
<ParcelTable
parcels={parcels}
networkStatus={networkStatus}
handleAction={handleAction}
/>
</Container>
);
}

export default Foreclosure;
Loading