Skip to content

Commit

Permalink
Merge pull request #28 from Propy/feat/signal-interest-offers
Browse files Browse the repository at this point in the history
feat(SignalInterest): render offers
  • Loading branch information
JayWelsh authored Nov 19, 2023
2 parents 8b82e6a + 64f6dc2 commit c0efbca
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 2 deletions.
153 changes: 153 additions & 0 deletions src/components/OfferList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import React from 'react';

import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';

import { shortenAddress } from '@usedapp/core'

import { Theme } from '@mui/material/styles';
import makeStyles from '@mui/styles/makeStyles';
import Typography from '@mui/material/Typography';
import createStyles from '@mui/styles/createStyles';

import OfferIcon from '@mui/icons-material/ConnectWithoutContact';

import { utils } from "ethers";

import { PropsFromRedux } from '../containers/EventHistoryContainer';

import LinkWrapper from './LinkWrapper';

import {
IOfferRecord,
NetworkName,
} from '../interfaces';

import {
PROPY_LIGHT_GREY,
} from '../utils/constants';

import {
getEtherscanLinkByNetworkName,
priceFormat,
} from '../utils';

dayjs.extend(advancedFormat);

const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
position: 'relative',
},
content: {
width: '100%',
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
overflowX: 'scroll',
},
offerRecord: {
width: '100%',
display: 'flex',
justifyContent: 'flex-start',
marginBottom: theme.spacing(2),
},
eventIconOuterContainer: {
marginRight: theme.spacing(2),
},
eventIconInnerContainer: {
backgroundColor: PROPY_LIGHT_GREY,
width: 40,
height: 40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '50%',
color: '#38a5fc',
},
eventRecordSummaryContainer: {
whiteSpace: 'nowrap',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
},
eventRecordSummaryLineOne: {
marginBottom: theme.spacing(0.5),
},
}),
);

interface ITokenOfferList {
offerRecords: IOfferRecord[] | null
}

const getOfferIcon = () => {
return <OfferIcon color="inherit" />;
}

const renderAddress = (value: string) => {
let innerElement = <Typography variant="body1" style={{fontWeight: 'bold', lineHeight: 1}}>{shortenAddress(value)}</Typography>;
let link = getEtherscanLinkByNetworkName(NetworkName["ethereum"], value, 'address');
if(link) {
return (
<LinkWrapper external={true} link={link}>
{innerElement}
</LinkWrapper>
)
}
return innerElement
}

const getEventSummaryLineEntryOne = (event: IOfferRecord) => {
// mint
return (
<div style={{display: 'flex'}}>
{renderAddress(event.user_address)}
<Typography variant="body1" style={{lineHeight: 1}}>&nbsp;offered <strong>{priceFormat(Number(utils.formatUnits(event.offer_token_amount, event.offer_token.decimals)), 2, event.offer_token.symbol, false)}</strong>&nbsp;</Typography>
</div>
);
}

const getEventSummaryLineEntryTwo = (event: IOfferRecord) => {
return (
<div style={{display: 'flex'}}>
<Typography variant="overline" style={{lineHeight: 1, textTransform: 'none'}}>{event.timestamp_unix ? dayjs.unix(Number(event.timestamp_unix)).format('MMM-D-YYYY hh:mm A') : 'loading...'}</Typography>
</div>
);
}

const OfferList = (props: PropsFromRedux & ITokenOfferList) => {
const classes = useStyles();

const {
offerRecords,
} = props;

return (
<>
<div className={classes.root}>
<div className={classes.content}>
{offerRecords && offerRecords.sort((a, b) => {
return Number(b.timestamp_unix) - Number(a.timestamp_unix)
}).map((offerRecord, index) =>
<div className={classes.offerRecord} key={`offer-list-${index}-${offerRecord.token_id}`}>
<div className={classes.eventIconOuterContainer}>
<div className={classes.eventIconInnerContainer}>
{getOfferIcon()}
</div>
</div>
<div className={classes.eventRecordSummaryContainer}>
<div className={classes.eventRecordSummaryLineOne}>
{getEventSummaryLineEntryOne(offerRecord)}
</div>
{getEventSummaryLineEntryTwo(offerRecord)}
</div>
</div>
)}
</div>
</div>
</>
)
};

export default OfferList;
5 changes: 5 additions & 0 deletions src/components/SignalInterest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ interface ISignalInterest {
tokenAddress: string,
tokenId: string,
tokenNetwork: string,
onSuccess?: () => void,
}

const SignalInterest = (props: PropsFromRedux & ISignalInterest) => {
Expand All @@ -95,6 +96,7 @@ const SignalInterest = (props: PropsFromRedux & ISignalInterest) => {
tokenAddress,
tokenId,
tokenNetwork,
onSuccess,
} = props;

const signOfferMessage = async (proAmount: string) => {
Expand Down Expand Up @@ -129,6 +131,9 @@ const SignalInterest = (props: PropsFromRedux & ISignalInterest) => {
let triggerSignedMessageActionResponse = await SignerService.validateSignedMessageAndPerformAction(messageForSigning, signedMessage, signerAccount);
console.log({triggerSignedMessageActionResponse});
if(triggerSignedMessageActionResponse.status) {
if(onSuccess) {
onSuccess();
}
toast.success("Offer placed successfully!");
setShowDialog(false);
}
Expand Down
19 changes: 19 additions & 0 deletions src/containers/OfferListContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { connect, ConnectedProps } from 'react-redux';

import OfferList from '../components/OfferList';

interface RootState {
isConsideredMobile: boolean
isConsideredMedium: boolean
}

const mapStateToProps = (state: RootState) => ({
isConsideredMobile: state.isConsideredMobile,
isConsideredMedium: state.isConsideredMedium,
})

const connector = connect(mapStateToProps, {})

export type PropsFromRedux = ConnectedProps<typeof connector>

export default connector(OfferList)
10 changes: 10 additions & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,14 @@ export interface INonceResponse {
nonce: number;
salt: string;
}
}

export interface IOfferRecord {
asset_address: string;
offer_token_address: string;
offer_token_amount: string;
timestamp_unix: number;
token_id: string;
user_address: string;
offer_token: IAssetRecord;
}
13 changes: 11 additions & 2 deletions src/pages/SingleTokenPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import GenericTitleContainer from '../containers/GenericTitleContainer';
import EventHistoryContainer from '../containers/EventHistoryContainer';
import TokenMetadataTimelineContainer from '../containers/TokenMetadataTimelineContainer';
import SignalInterestContainer from '../containers/SignalInterestContainer';
import OfferListContainer from '../containers/OfferListContainer';

import LinkWrapper from '../components/LinkWrapper';

Expand All @@ -36,6 +37,7 @@ import {
ITokenMetadata,
ITransferEventERC20Record,
ITransferEventERC721Record,
IOfferRecord,
TokenStandard,
} from '../interfaces';

Expand Down Expand Up @@ -111,6 +113,7 @@ const SingleTokenPage = () => {

const [tokenRecord, setTokenRecord] = useState<IAssetRecord | null>(null);
const [tokenEventRecord, setTokenEventRecord] = useState<ITransferEventERC721Record[] | ITransferEventERC20Record[] | null>(null);
const [tokenOfferList, setTokenOfferList] = useState<IOfferRecord[] | null>(null);
const [tokenMetadata, setTokenMetadata] = useState<ITokenMetadata | null>(null);
const [fetchIndex, setFetchIndex] = useState<number>(0);
const [isMetadataRefreshing, setIsMetadataRefreshing] = useState<boolean>(false);
Expand Down Expand Up @@ -146,6 +149,9 @@ const SingleTokenPage = () => {
if(tokenRecordQueryResponse?.data?.transfer_events_erc20) {
setTokenEventRecord(tokenRecordQueryResponse?.data?.transfer_events_erc20);
}
if(tokenRecordQueryResponse?.data?.offchain_offers) {
setTokenOfferList(tokenRecordQueryResponse?.data?.offchain_offers);
}
if(tokenRecordQueryResponse?.data?.metadata) {
let metadata = JSON.parse(tokenRecordQueryResponse?.data?.metadata);
// // temp timeline shim for testing design
Expand Down Expand Up @@ -261,8 +267,11 @@ const SingleTokenPage = () => {
{tokenEventRecord && <EventHistoryContainer eventRecords={tokenEventRecord} assetRecord={tokenRecord} />}
{allowSignalInterest && tokenId && tokenAddress && network &&
<>
<GenericTitleContainer variant={"h5"} paddingBottom={8} marginTop={24} marginBottom={12} title="Make an Offer"/>
<SignalInterestContainer tokenId={tokenId} tokenAddress={tokenAddress} tokenNetwork={network} />
<GenericTitleContainer variant={"h5"} paddingBottom={8} marginTop={24} marginBottom={12} title="Offers"/>
<SignalInterestContainer onSuccess={() => setFetchIndex(fetchIndex + 1)} tokenId={tokenId} tokenAddress={tokenAddress} tokenNetwork={network} />
<div className={classes.sectionSpacer}>
{tokenEventRecord && <OfferListContainer offerRecords={tokenOfferList} />}
</div>
</>
}
</>
Expand Down

0 comments on commit c0efbca

Please sign in to comment.