Skip to content

Commit

Permalink
adding rental details popup and rendering more nfts
Browse files Browse the repository at this point in the history
  • Loading branch information
cyficowley committed Apr 30, 2022
1 parent 4d41106 commit f638a13
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 79 deletions.
74 changes: 15 additions & 59 deletions frontend/src/client/components/ExploreRentals.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useState, useEffect } from "react";
import { NftWithMetadata, Nft, AvaliabilityStatus, Attribute } from "../../../types/nftTypes.js";
import { NftWithMetadata, Nft } from "../../../types/nftTypes.js";
import { ListingPanel } from "./ListingPanel";
import { Popup } from "./Popup";
import { RentDetails } from "./RentDetails";
import { useMoralisQuery } from "react-moralis";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { getNFTs, getNFTMetadata } from "../lib/web3";
import { mergeNftsWithMetadata } from "../lib/fetchNft";

export const ExploreRentals = () => {
const [dateFilterVisible, setDateFilterVisible] = useState(false);
Expand All @@ -13,64 +15,11 @@ export const ExploreRentals = () => {
const [costFilterVisible, setCostFilterVisible] = useState(false);
const [startCost, setStartCost] = useState<null | number>(null);
const [endCost, setEndCost] = useState<null | number>(null);
const { data: rawNftListings, error, isLoading } = useMoralisQuery("Listing", query => query.limit(8), [], {});

const { data: rawNftListings, error, isLoading } = useMoralisQuery("Listing", query => query.limit(8), [], {});
const [nfts, setNfts] = useState<NftWithMetadata[]>([]);

const getMetadataForAllNfts = async (nfts: Nft[]) => {
const awaitables = [];
for (const nft of nfts) {
awaitables.push(getNFTMetadata(nft.specification.collection, nft.specification.id));
}
return Promise.all(awaitables);
};

const calculateAvaliabiltyStatus = (nft: Nft) => {
const now = new Date();
if (nft.listing.datesForRent.some(date => date.startDate <= now && date.endDate >= now)) {
return AvaliabilityStatus.Avaliabile;
} else {
return AvaliabilityStatus.Unavaliabile;
}
};

const mapIpfsToUrl = (ipfs: string) => {
return `https://ipfs.io/ipfs/${ipfs.slice(7)}`;
};

const mergeNftsWithMetadata = async (nfts: Nft[]) => {
let nftsWithMetadata = [];
const metadatas = await getMetadataForAllNfts(nfts);
for (let i = 0; i < nfts.length; i++) {
const metadata = metadatas[i].metadata!;

let attributes: Attribute[] = [];
if (metadata.attributes) {
attributes = metadata.attributes.map(attribute => {
return {
traitType: attribute.trait_type,
value: attribute.value,
};
});
}

let image = metadata.image!;

if (image.startsWith("ipfs://")) {
image = mapIpfsToUrl(image);
}

const nftWithMetadata = {
nft: nfts[i],
name: metadata.name!,
image,
attributes,
avaliability: { status: calculateAvaliabiltyStatus(nfts[i]) },
};
nftsWithMetadata.push(nftWithMetadata);
}
setNfts(nftsWithMetadata);
};
const [selectedNft, setSelectedNft] = useState<NftWithMetadata | null>(null);

useEffect(() => {
const nftListings: Nft[] = rawNftListings.map(nft => {
Expand All @@ -79,7 +28,7 @@ export const ExploreRentals = () => {
specification: nft.attributes.nftSpecification,
};
});
mergeNftsWithMetadata(nftListings);
mergeNftsWithMetadata(nftListings).then(nftsWithMetadata => setNfts(nftsWithMetadata));
}, [rawNftListings]);

if (isLoading) {
Expand Down Expand Up @@ -197,14 +146,21 @@ export const ExploreRentals = () => {

return (
<>
{selectedNft && (
<Popup closeHandler={() => setSelectedNft(null)}>
<RentDetails nft={selectedNft} />
</Popup>
)}
<div className="container">
<div className="flex pb-3">
<div className="mr-4">{renderDateFilter()}</div>
<div>{renderCostFilter()}</div>
</div>
<div className="grid grid-cols-4 gap-4 w-full">
{filteredList.map((nft, index) => (
<ListingPanel nft={nft} key={index} />
<div onClick={() => setSelectedNft(nft)} key={index}>
<ListingPanel nft={nft} />
</div>
))}
</div>
</div>
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/client/components/ListingPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import { NftWithMetadata, Avaliability, AvaliabilityStatus } from "../../../types/nftTypes.js";

export const ListingPanel = ({ nft }: { nft: NftWithMetadata }) => {
export const ListingPanel = ({ nft, pureNft = false }: { nft: NftWithMetadata; pureNft?: boolean }) => {
const renderAvaliability = (availability: Avaliability) => {
let statusText;
switch (availability.status) {
Expand Down Expand Up @@ -41,7 +41,7 @@ export const ListingPanel = ({ nft }: { nft: NftWithMetadata }) => {
<div className="w-full flex items-center pb-2">
<h1 className="text-lg font-bold">{nft.name}</h1>
<div className="flex-grow pl-1"></div>
<p>ETH({nft.nft.listing.pricePerDay}) / day</p>
{!pureNft && <p>ETH({nft.nft.listing.pricePerDay}) / day</p>}
</div>
{nft.attributes.map(attribute => (
<div
Expand All @@ -51,8 +51,12 @@ export const ListingPanel = ({ nft }: { nft: NftWithMetadata }) => {
<span className="font-bold">{attribute.traitType}</span>: {attribute.value}
</div>
))}
<p>{nft.nft.listing.description}</p>
<div className="pt-2">{renderAvaliability(nft.avaliability)}</div>
{!pureNft && (
<>
<p>{nft.nft.listing.description}</p>
<div className="pt-2">{renderAvaliability(nft.avaliability)}</div>
</>
)}
</div>
</div>
);
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/client/components/Popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { ReactNode } from "react";

export const Popup = ({ closeHandler, children }: { closeHandler: () => void; children: ReactNode }) => {
return (
<div
className="fixed h-screen w-screen top-0 left-0 flex justify-center items-center z-10"
style={{ backgroundColor: "rgba(0,0,0,.2)" }}
onClick={e => {
if (e.target === e.currentTarget) {
closeHandler();
}
}}
>
<div className="bg-white shadow relative p-4">
<div
className="absolute right-0 top-0 cursor-pointer p-3 text-xl"
style={{ fontFamily: "Arial" }}
onClick={closeHandler}
>
&#x2716;
</div>
{children}
</div>
</div>
);
};
32 changes: 32 additions & 0 deletions frontend/src/client/components/RentDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useState } from "react";
import { NftWithMetadata } from "../../../types/nftTypes.js";
import DatePicker from "react-datepicker";

export const RentDetails = ({ nft }: { nft: NftWithMetadata }) => {
const [dateRange, setDateRange] = useState<[Date, Date]>([new Date(), new Date()]);
const [startDate, endDate] = dateRange;

return (
<div>
<h1 className="text-3xl font-bold">{nft.name}</h1>
<p className="text-lg">{nft.nft.listing.description}</p>
<h3 className="text-xl">Pick your date:</h3>
<DatePicker
selectsRange={true}
startDate={startDate}
endDate={endDate}
onChange={(update: any) => {
setDateRange(update);
}}
minDate={new Date()}
showDisabledMonthNavigation
inline
/>
<h3 className="text-xl">Price per day: {nft.nft.listing.pricePerDay} ETH</h3>
<h3 className="text-xl">Collateral: {nft.nft.listing.collateral} ETH</h3>
<button className="mt-2 w-48 bg-indigo-800 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md">
Rent!
</button>
</div>
);
};
59 changes: 59 additions & 0 deletions frontend/src/client/lib/fetchNft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

import { getNFTMetadata } from "./web3";
import { NftWithMetadata, Nft, AvaliabilityStatus, Attribute } from "../../../types/nftTypes.js";


const getMetadataForAllNfts = async (nfts: Nft[]) => {
const awaitables = [];
for (const nft of nfts) {
awaitables.push(getNFTMetadata(nft.specification.collection, nft.specification.id));
}
return Promise.all(awaitables);
};

const calculateAvaliabiltyStatus = (nft: Nft) => {
const now = new Date();
if (nft.listing.datesForRent.some(date => date.startDate <= now && date.endDate >= now)) {
return AvaliabilityStatus.Avaliabile;
} else {
return AvaliabilityStatus.Unavaliabile;
}
};

const mapIpfsToUrl = (ipfs: string) => {
return `https://ipfs.io/ipfs/${ipfs.slice(7)}`;
};

export const mergeNftsWithMetadata = async (nfts: Nft[]) => {
let nftsWithMetadata = [];
const metadatas = await getMetadataForAllNfts(nfts);
for (let i = 0; i < nfts.length; i++) {
const metadata = metadatas[i].metadata!;

let attributes: Attribute[] = [];
if (metadata.attributes) {
attributes = metadata.attributes.map(attribute => {
return {
traitType: attribute.trait_type,
value: attribute.value,
};
});
}

let image = metadata.image!;

if (image.startsWith("ipfs://")) {
image = mapIpfsToUrl(image);
}

const nftWithMetadata: NftWithMetadata = {
nft: nfts[i],
name: metadata.name!,
image,
attributes,
avaliability: { status: calculateAvaliabiltyStatus(nfts[i]) },
};
nftsWithMetadata.push(nftWithMetadata);
}
return nftsWithMetadata;
};
Loading

1 comment on commit f638a13

@vercel
Copy link

@vercel vercel bot commented on f638a13 Apr 30, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.