Skip to content

Commit

Permalink
Can upload nfts and list them
Browse files Browse the repository at this point in the history
  • Loading branch information
cyficowley committed Apr 30, 2022
1 parent 26b0361 commit 4d41106
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 73 deletions.
126 changes: 85 additions & 41 deletions frontend/src/client/components/ExploreRentals.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,93 @@
import React, { useState, forwardRef } from "react";
import { NftWithMetadata, AvaliabilityStatus } from "../../../types/nftTypes.js";
import React, { useState, useEffect } from "react";
import { NftWithMetadata, Nft, AvaliabilityStatus, Attribute } from "../../../types/nftTypes.js";
import { ListingPanel } from "./ListingPanel";
import { useMoralisQuery } from "react-moralis";
import DatePicker from "react-datepicker";

import "react-datepicker/dist/react-datepicker.css";
import { getNFTs, getNFTMetadata } from "../lib/web3";

export const ExploreRentals = () => {
const nft: NftWithMetadata = {
nft: {
listing: {
name: "Jelly bean",
description: "it has a crown and stuff",
datesForRent: [
{
startDate: new Date(),
endDate: new Date(2022, 6, 1),
},
],
pricePerDay: 1,
collateral: 2,
},
nftSpecification: {
nftCollection: "0xa755a670aaf1fecef2bea56115e65e03f7722a79",
nftId: "0",
},
},
image: "https://ipfs.io/ipfs/QmNj6UmToxxnJFqW13GkG3NHX1FXtHsHwAbuB8uQasPZUm",
avaliability: {
status: AvaliabilityStatus.Avaliabile,
},
attributes: [{ traitType: "asdf", value: "asdf2" }],
};

let nfts = [];
for (let i = 0; i < 8; i++) {
nfts.push(nft);
}

const [dateFilterVisible, setDateFilterVisible] = useState(false);

const [dateRange, setDateRange] = useState<[null | Date, null | Date]>([null, null]);
const [startDate, endDate] = dateRange;

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 [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);
};

useEffect(() => {
const nftListings: Nft[] = rawNftListings.map(nft => {
return {
listing: nft.attributes.listing,
specification: nft.attributes.nftSpecification,
};
});
mergeNftsWithMetadata(nftListings);
}, [rawNftListings]);

if (isLoading) {
return <div>Loading</div>;
}
if (error) {
return <div>ERROR {error}</div>;
}

const renderDateFilter = () => {
let text = "Filter by date";
Expand Down Expand Up @@ -133,20 +176,21 @@ export const ExploreRentals = () => {
);
};

let filteredList = [...nfts];
if (startDate && endDate) {
nfts = nfts.filter(nft => {
filteredList = filteredList.filter(nft => {
return nft.nft.listing.datesForRent.some(avaliableDate => {
return avaliableDate.startDate <= startDate && avaliableDate.endDate >= endDate;
});
});
}
if (startCost) {
nfts = nfts.filter(nft => {
filteredList = filteredList.filter(nft => {
return nft.nft.listing.pricePerDay >= startCost;
});
}
if (endCost) {
nfts = nfts.filter(nft => {
filteredList = filteredList.filter(nft => {
return nft.nft.listing.pricePerDay <= endCost;
});
}
Expand All @@ -159,8 +203,8 @@ export const ExploreRentals = () => {
<div>{renderCostFilter()}</div>
</div>
<div className="grid grid-cols-4 gap-4 w-full">
{nfts.map(nft => (
<ListingPanel nft={nft} />
{filteredList.map((nft, index) => (
<ListingPanel nft={nft} key={index} />
))}
</div>
</div>
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/client/components/ListingPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,15 @@ export const ListingPanel = ({ nft }: { nft: NftWithMetadata }) => {
</div>
<div>
<div className="w-full flex items-center pb-2">
<h1 className="text-lg font-bold">{nft.nft.listing.name}</h1>
<h1 className="text-lg font-bold">{nft.name}</h1>
<div className="flex-grow pl-1"></div>
<p>ETH({nft.nft.listing.pricePerDay}) / day</p>
</div>
{nft.attributes.map(attribute => (
<div className="inline-block bg-slate-200 px-2 py-1 rounded-xl text-xs mr-2 mb-2">
<div
className="inline-block bg-slate-200 px-2 py-1 rounded-xl text-xs mr-2 mb-2"
key={attribute.traitType + attribute.value}
>
<span className="font-bold">{attribute.traitType}</span>: {attribute.value}
</div>
))}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/client/entry-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { MoralisProvider } from "react-moralis";
ReactDOM.hydrate(
<React.StrictMode>
<MoralisProvider
serverUrl="https://9soacisxu22u.usemoralis.com:2053/server"
appId="fqMyf2nTsjNW99WTHFmm5GwkXq3skEXNtxRqfSqP"
serverUrl="https://rvzubkq2tkj7.usemoralis.com:2053/server"
appId="Z9yUNCpsZ78x08I4ZZYu14TJBnqryBxOxRe4l2lz"
>
<BrowserRouter>
<App />
Expand Down
115 changes: 91 additions & 24 deletions frontend/src/client/pages/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React from "react";
import { Footer } from "../components/Footer";
import { Navbar } from "../components/Navbar";
import { DayPicker } from "react-day-picker";
import { Nft } from "../../../types/nftTypes.js";
import { useMoralis } from "react-moralis";
import "react-day-picker/dist/style.css";

const buttonStyle = "w-48 bg-indigo-800 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-md";
Expand All @@ -15,30 +17,58 @@ const FormSection: React.FC<{ center?: boolean }> = ({ center, children }) => {
const Divider = () => <hr className="border-slate-300" />;

const List = () => {
const Formik = useFormik({
const { Moralis } = useMoralis();

// A complex subclass of Moralis.Object
const Listing = Moralis.Object.extend(
"Listing",
{},
{
create: function (nft: Nft) {
const listing = new Listing();
listing.set("listing", nft.listing);
listing.set("nftSpecification", nft.specification);
return listing;
},
},
);
const submitListing = (nft: Nft) => {
const listing = Listing.create(nft);
listing.save().then((e: any) => {
console.log(e);
});
};

const formik = useFormik({
initialValues: {
walletConnected: false,
nftSelected: false,
name: "",
collection: "",
id: "",
description: "",
rentalDates: "",
listingPrice: 0,
pricePerDay: 0,
collateral: 0,
gracePeriod: 0,
collateralPayback: "",
image: null,
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
submitListing({
listing: {
description: values.description,
datesForRent: [],
pricePerDay: values.pricePerDay,
collateral: values.collateral,
},
specification: {
collection: values.collection,
id: values.id,
},
});
},
});

return (
<div className="flex bg-white-100 items-center flex-col justify-between h-screen">
<Navbar />
<form onSubmit={Formik.handleSubmit} className="flex w-2/3 mr-auto p-8 flex-col gap-8">
<form onSubmit={formik.handleSubmit} className="flex w-2/3 mr-auto p-8 flex-col gap-8">
<h1 className="font-extrabold text-slate-800 sm:text-5xl md:text-6xl">List a Rental</h1>
<div className="flex flex-row">
{/* <div className="flex flex-row">
<button className={`${buttonStyle} rounded-tr-none rounded-br-none`}>Connect Wallet</button>
<div className="flex items-center justify-center w-16 h-full border-2 border-indigo-800 rounded-tr-md rounded-br-md">
<input type="checkbox" className="w-4 h-4"></input>
Expand All @@ -49,39 +79,76 @@ const List = () => {
<div className="flex items-center justify-center w-16 h-full border-2 border-indigo-800 rounded-tr-md rounded-br-md">
<input type="checkbox" className="w-4 h-4"></input>
</div>
</div>
<FormSection center>
<label htmlFor="name" className={labelStyle}>
Listing Name
</div> */}
<FormSection>
<label htmlFor="collection" className={labelStyle}>
Collection Address & ID
</label>
<input type="text" id="name" name="name" className={`${inputStyle}`}></input>
<input
id="collection"
name="collection"
onChange={formik.handleChange}
value={formik.values.collection}
className={`${inputStyle}`}
></input>
<input
id="id"
name="id"
onChange={formik.handleChange}
value={formik.values.id}
className={`${inputStyle}`}
></input>
</FormSection>
<FormSection>
<label htmlFor="description" className={labelStyle}>
Listing Description
</label>
<textarea id="description" name="description" className={`${inputStyle} h-48`}></textarea>
<textarea
id="description"
name="description"
onChange={formik.handleChange}
value={formik.values.description}
className={`${inputStyle} h-48`}
></textarea>
</FormSection>
<Divider />
{/* <Divider />
<FormSection center>
<label className={labelStyle}>Dates for Rental</label>
<DayPicker />
<DayPicker />
</FormSection>
<Divider />
<Divider /> */}
<FormSection center>
<label className={labelStyle}>Price & Collateral</label>
<input type="text" className={inputStyle}></input>
<input type="text" className={inputStyle}></input>
<input
id="pricePerDay"
name="pricePerDay"
type="number"
step="any"
min="0"
onChange={formik.handleChange}
value={formik.values.pricePerDay}
className={inputStyle}
></input>
<input
id="collateral"
name="collateral"
type="number"
step="any"
min="0"
onChange={formik.handleChange}
value={formik.values.collateral}
className={inputStyle}
></input>
</FormSection>
<FormSection center>
{/* <FormSection center>
<label className={labelStyle}>Grace Period (Days)</label>
<input type="text" className={inputStyle}></input>
</FormSection>
<FormSection center>
<label className={labelStyle}>Collateral Payback</label>
<input type="text" className={inputStyle}></input>
</FormSection>
</FormSection> */}
<button className={buttonStyle} type="submit">
List my Rental!
</button>
Expand Down
8 changes: 4 additions & 4 deletions frontend/types/nftTypes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// declare modules here

export interface NftSpecification {
nftCollection: string;
nftId: string;
collection: string;
id: string;
}

export interface RentContract {
Expand All @@ -20,7 +20,6 @@ export interface AvaliableDates {
}

export interface Listing {
name: string;
description: string;
datesForRent: AvaliableDates[];
pricePerDay: number;
Expand All @@ -30,7 +29,7 @@ export interface Listing {
export interface Nft {
contract?: RentContract;
listing: Listing;
nftSpecification: NftSpecification;
specification: NftSpecification;
}

export enum AvaliabilityStatus {
Expand All @@ -51,6 +50,7 @@ export interface Attribute {

export interface NftWithMetadata {
nft: Nft;
name: string;
image: string;
avaliability: Avaliability;
attributes: Attribute[];
Expand Down

1 comment on commit 4d41106

@vercel
Copy link

@vercel vercel bot commented on 4d41106 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.