Skip to content

Commit

Permalink
chore: [#3] disable claim button when no more to claim
Browse files Browse the repository at this point in the history
- extracted parcel nft loading logic
  • Loading branch information
mdnorman committed May 12, 2022
1 parent 492db52 commit 27b32ac
Show file tree
Hide file tree
Showing 17 changed files with 410 additions and 221 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:
- name: Check out code
uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
run: yarn install --frozen-lockfile
- name: Build
run: npm run build
run: yarn build
- name: Lint
run: npm run lint
run: yarn lint
14 changes: 0 additions & 14 deletions components/Button/Button.tsx

This file was deleted.

1 change: 0 additions & 1 deletion components/Button/index.tsx

This file was deleted.

27 changes: 27 additions & 0 deletions components/ClaimButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import DefaultButton, { DefaultButtonProps } from './common/DefaultButton';

export interface ClaimButtonProps extends DefaultButtonProps {
allowance: number;
walletAlreadyClaimed: number;
}

const ClaimButton = ({ allowance, walletAlreadyClaimed, disabled, ...rest }: ClaimButtonProps) => {
const getClaimButtonText = () => {
if (walletAlreadyClaimed === 0 && allowance === 0) {
return 'CLAIM PLOTS';
} else if (walletAlreadyClaimed > 0) {
return `${walletAlreadyClaimed} PLOTS CLAIMED`;
} else if (allowance > 0) {
return `CLAIM ${allowance} PLOTS`;
}
return '';
};

return (
<DefaultButton {...rest} disabled={disabled || allowance === 0}>
{getClaimButtonText()}
</DefaultButton>
);
};
export default ClaimButton;
2 changes: 1 addition & 1 deletion components/ClaimModal/ClaimModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FC, useState } from 'react';
import Modal from 'react-modal';

import { useModal } from '../../hooks/useModal';
import { AGREEMENT_IPFS_URL } from '../../contants';
import { AGREEMENT_IPFS_URL } from '../../constants/other';

type ClaimModalProps = {
eligibleNftsCount: number;
Expand Down
2 changes: 1 addition & 1 deletion components/ParcelProperty/ParcelProperty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FC } from 'react';
export type ParcelPropertyProps = {
name: string;
value: string;
tooltip: string;
tooltip?: string;
iconPath: string;
};
export const ParcelProperty: FC<ParcelPropertyProps> = ({ name, value, tooltip, iconPath }) => (
Expand Down
8 changes: 8 additions & 0 deletions components/common/DefaultButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';

export interface DefaultButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

const DefaultButton = ({ disabled, ...rest }: DefaultButtonProps) => (
<button {...rest} disabled={disabled} className={disabled ? 'border-button' : ''} />
);
export default DefaultButton;
17 changes: 17 additions & 0 deletions constants/other.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ZERO_ADDRESS } from '@citydao/parcel-contracts/dist/src/constants/accounts';
import { addresses } from '../data/whiteListedAddresses';

export const MAX_NFT_TO_MINT = Object.values(addresses).reduce((prev, curr) => prev + curr, 0);

const AGREEMENT_IPFS_HASH = 'QmUbFb12ZEAyoqGUEsnS8fxh78nowEqNwvn7BbAfryRRay';
export const AGREEMENT_IPFS_URL = `https://ipfs.io/ipfs/${AGREEMENT_IPFS_HASH}`;

export const PARCEL0_NFT_CONTRACT_ADDRESSES: { [key: number]: string } = {
1: ZERO_ADDRESS,
4: '0x209723a65844093Ad769d557a22742e0f661959d',
};

export enum VIEWS {
'INITIAL_VIEW',
'MINTED_NFTS',
}
24 changes: 5 additions & 19 deletions containers/ParcelProperties/ParcelProperties.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
import { FC } from "react";
import {
ParcelProperty,
ParcelPropertyProps,
} from "../../components/ParcelProperty";
import { AGREEMENT_IPFS_URL } from "../../contants";
import { FC } from 'react';
import { ParcelProperty, ParcelPropertyProps } from '../../components/ParcelProperty';
import { AGREEMENT_IPFS_URL } from '../../constants/other';
export const ParcelProperties: FC<{
parcelProperties: ParcelPropertyProps[];
}> = ({ parcelProperties }) => (
<div className="properties">
{parcelProperties.map(({ name, value, tooltip, iconPath }, index) => (
<ParcelProperty
name={name}
key={index}
value={value}
tooltip={tooltip}
iconPath={iconPath}
/>
<ParcelProperty name={name} key={index} value={value} tooltip={tooltip} iconPath={iconPath} />
))}
<a
href={AGREEMENT_IPFS_URL}
target="_blank"
className="link-external"
rel="noreferrer"
>
<a href={AGREEMENT_IPFS_URL} target="_blank" className="link-external" rel="noreferrer">
VIEW FULL LICENSE
</a>
</div>
Expand Down
11 changes: 0 additions & 11 deletions contants.ts

This file was deleted.

21 changes: 10 additions & 11 deletions context/StateProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { reducer, Actions } from "../reducer/";
import React, { createContext, ReactNode, useContext, useReducer } from "react";
import { Provider } from '@ethersproject/abstract-provider';
import { JsonRpcProvider } from '@ethersproject/providers';
import React, { createContext, ReactNode, useContext, useReducer } from 'react';
import { Actions, reducer } from '../reducer/';

export type InitialStateType = {
provider?: any;
web3Provider?: any;
account?: string | null;
chainId?: number | null;
provider: Provider | null;
web3Provider: JsonRpcProvider | null;
account: string | null;
chainId: number | null;
};

const initialState: InitialStateType = {
Expand All @@ -27,11 +30,7 @@ type AppProviderProps = {
export const AppProvider: React.FC<AppProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
return <AppContext.Provider value={{ state, dispatch }}>{children}</AppContext.Provider>;
};

export const useAppContext = () => useContext(AppContext);
157 changes: 157 additions & 0 deletions hooks/contractHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { ZERO_ADDRESS } from '@citydao/parcel-contracts/dist/src/constants/accounts';
import { JsonRpcProvider, Provider } from '@ethersproject/providers';
import { Contract, ContractFactory, Signer } from 'ethers';
import { useEffect, useMemo, useState } from 'react';
import useWallet from './useWallet';

export interface ContractLoaderHook<C extends Contract, K extends KeyOfGetterFunction<C>> {
contract?: C;
values?: ContractValues<C, K>;
refetch: () => Promise<void>;
}

export type ContractValues<C extends Contract, K extends KeyOfGetterFunction<C>> = {
[P in K]: Awaited<ReturnType<C[K]>>;
};

export type KeyOfType<T, V> = keyof {
[P in keyof T as T[P] extends V ? P : never]: any;
};

export type KeyOfGetterFunction<T> = KeyOfType<T, () => any>;

export const useContractLoader = <
F extends ContractFactory,
C extends FactoryContract<F>,
K extends KeyOfGetterFunction<C>,
>(
factory: F,
address: string,
keys: K[] = [],
): ContractLoaderHook<C, K> => {
const { web3Provider } = useWallet();

const contract = useMemo(
() => attachContract<F, C>(factory, address, web3Provider) || undefined,
[factory, address, web3Provider],
);
const [values, setValues] = useState<ContractValues<C, K>>();

useEffect(() => {
// noinspection JSIgnoredPromiseFromCall
fetchValues();
}, [contract]);

const fetchValues = async () => {
if (!contract) {
setValues(undefined);
return;
}

const fetchedValues = await Promise.all(keys.map((key) => contract[key]()));

setValues(
fetchedValues.reduce((acc, fetchedValue, index) => {
acc[keys[index]] = fetchedValue;
return acc;
}, {} as ContractValues<C, K>),
);
};

return { contract, values, refetch: fetchValues };
};

export const useInterfaceLoader = <C extends Contract, K extends KeyOfGetterFunction<C>>(
factory: InterfaceFactoryConnector<C>,
address: string,
keys: K[] = [],
): ContractLoaderHook<C, K> => {
const { web3Provider } = useWallet();

const contract = useMemo(
() => attachInterface<C>(factory, address, web3Provider) || undefined,
[address, web3Provider],
);
const [values, setValues] = useState<ContractValues<C, K>>();

useEffect(() => {
// noinspection JSIgnoredPromiseFromCall
fetchValues();
}, [contract]);

const fetchValues = async () => {
if (!contract) {
setValues(undefined);
return;
}

const fetchedValues = await Promise.all(keys.map((key) => contract[key]()));

setValues(
fetchedValues.reduce((acc, fetchedValue, index) => {
acc[keys[index]] = fetchedValue;
return acc;
}, {} as ContractValues<C, K>),
);
};

return { contract, values, refetch: fetchValues };
};

export interface EthereumProviderHook {
provider: Provider | null;
signer: Signer | null;
}

export const useEthereumProvider = (): EthereumProviderHook => {
const { web3Provider } = useWallet();
const signer = web3Provider?.getSigner();

return { provider: web3Provider, signer: signer || null };
};

export type FactoryContract<F extends ContractFactory> = Contract & Awaited<ReturnType<F['deploy']>>;

export const attachContract = <F extends ContractFactory, C extends FactoryContract<F>>(
factory: F,
address: string,
provider: JsonRpcProvider | null,
): C | null => {
if (address === '' || address === ZERO_ADDRESS) {
return null;
}

const signer = provider?.getSigner();
if (signer) {
return factory.attach(address).connect(signer) as C;
}

if (provider) {
return factory.attach(address).connect(provider) as C;
}

return null;
};

export type InterfaceFactoryConnector<C extends Contract> = (address: string, provider: Signer | Provider) => C;

export const attachInterface = <C extends Contract>(
connect: InterfaceFactoryConnector<C>,
address: string,
provider: JsonRpcProvider | null,
): C | null => {
if (address === '' || address === ZERO_ADDRESS) {
return null;
}

const signer = provider?.getSigner();
if (signer) {
return connect(address, signer) as C;
}

if (provider) {
return connect(address, provider) as C;
}

return null;
};
60 changes: 60 additions & 0 deletions hooks/parcelNFT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ParcelNFT__factory } from '@citydao/parcel-contracts/dist/types/contracts/factories/ParcelNFT__factory';
import { ParcelNFT } from '@citydao/parcel-contracts/dist/types/contracts/ParcelNFT';
import { ContractAddress } from '@citydao/parcel-contracts/src/constants/accounts';
import { useEffect, useState } from 'react';
import { addresses } from '../data/whiteListedAddresses';
import { useContractLoader } from './contractHooks';
import useWallet from './useWallet';

export interface ParcelNFTHook {
parcelNFTDetails: ParcelNFTDetails | null;
refetch: () => Promise<void>;
}

export interface ParcelNFTDetails {
parcelNFT: ParcelNFT;
totalSupply: number;
walletAlreadyClaimed: number;
allowance: number;
}

export const useParcelNFT = (contractAddress: ContractAddress): ParcelNFTHook => {
const { account } = useWallet();
const {
contract: parcelNFT,
values,
refetch: refetchValues,
} = useContractLoader(new ParcelNFT__factory(), contractAddress, ['totalSupply']);
const [walletAlreadyClaimed, setWalletAlreadyClaimed] = useState<number>(0);

const parcelNFTDetails: ParcelNFTDetails | null =
parcelNFT && values && account
? {
parcelNFT: parcelNFT,
walletAlreadyClaimed,
totalSupply: values.totalSupply.toNumber(),
allowance: addresses[account.toLowerCase()],
}
: null;

useEffect(() => {
// noinspection JSIgnoredPromiseFromCall
loadFields();
}, [account, parcelNFT]);

const loadFields = async () => {
if (!parcelNFT || !account) {
setWalletAlreadyClaimed(0);
return;
}

const alreadyClaimed = (await parcelNFT.alreadyClaimed(account)).toNumber();
setWalletAlreadyClaimed(alreadyClaimed);
};

const refetch = async () => {
await Promise.all([refetchValues(), loadFields()]);
};

return { parcelNFTDetails: parcelNFTDetails, refetch };
};
Loading

0 comments on commit 27b32ac

Please sign in to comment.