@smartinvoicexyz/hooks
Version:
Unified source for React hooks used across the Smart Invoice protocol.
131 lines (130 loc) • 5.68 kB
JavaScript
import { INVOICE_VERSION, SMART_INVOICE_UPDATABLE_ABI, TOASTS, } from '@smartinvoicexyz/constants';
import { waitForSubgraphSync } from '@smartinvoicexyz/graphql';
import { errorToastHandler, getResolverInfoByAddress, parseToDate, uriToDocument, } from '@smartinvoicexyz/utils';
import _ from 'lodash';
import { useCallback, useMemo, useState } from 'react';
import { parseUnits } from 'viem';
import { useChainId, usePublicClient, useSimulateContract, useWriteContract, } from 'wagmi';
import { useDetailsPin } from './useDetailsPin';
export const useAddMilestones = ({ address, localForm, invoice, toast, onTxSuccess, }) => {
const publicClient = usePublicClient();
const chainId = useChainId();
const { tokenMetadata, metadata, amounts, resolver } = _.pick(invoice, [
'tokenMetadata',
'metadata',
'amounts',
'resolver',
]);
const { getValues } = localForm;
const { milestones, document } = getValues();
const detailsData = useMemo(() => {
const { title, description, startDate, endDate, resolverType, klerosCourt, createdAt, documents, milestones: oldMilestones, } = metadata ?? {};
const now = createdAt
? Math.floor(parseToDate(createdAt).getTime() / 1000)
: Math.floor(new Date().getTime() / 1000);
const end = endDate
? Math.floor(parseToDate(endDate).getTime() / 1000)
: now + 60 * 60 * 24 * 30;
const start = startDate
? Math.floor(parseToDate(startDate).getTime() / 1000)
: now;
const newMilestones = [];
amounts?.forEach((_amount, i) => {
newMilestones.push({
id: _.join([`Milestone ${i + 1}`, now, INVOICE_VERSION], '-'),
title: oldMilestones?.[i].title ?? `Milestone ${i + 1}`,
description: oldMilestones?.[i].description ?? '',
createdAt: oldMilestones?.[i].createdAt ?? now,
endDate: oldMilestones?.[i].endDate ?? end,
});
});
milestones?.forEach((milestone, i) => {
newMilestones.push({
id: _.join([`Milestone ${i + newMilestones.length + 1}`, now, INVOICE_VERSION], '-'),
title: milestone.title ?? `Milestone ${i + newMilestones.length + 1}`,
description: milestone.description ?? '',
createdAt: now,
endDate: end,
});
});
const newResolverType = resolverType ??
getResolverInfoByAddress(address, chainId)?.id ??
'custom';
const newDocuments = documents
? [
...documents.map(d => ({
...uriToDocument(d.src),
createdAt: createdAt
? Math.floor(parseToDate(createdAt).getTime() / 1000)
: Math.floor(new Date().getTime() / 1000),
})),
]
: [];
const newDocument = uriToDocument(document);
if (newDocument) {
newDocuments.push(newDocument);
}
return {
version: INVOICE_VERSION,
id: _.join([title, now, INVOICE_VERSION], '-'),
title,
description,
documents: newDocuments,
startDate: start,
endDate: end,
createdAt: now,
milestones: newMilestones,
resolverType: newResolverType,
...(newResolverType === 'kleros' ? { klerosCourt } : {}),
};
}, [document, JSON.stringify(milestones), metadata, amounts, resolver]);
const { data: details, isLoading: detailsLoading } = useDetailsPin(detailsData);
const parsedMilestones = _.map(milestones, (milestone) => milestone.value !== '' && _.toNumber(milestone.value) > 0
? parseUnits(milestone.value, tokenMetadata?.decimals || 18)
: BigInt(0));
const { error: prepareError, isLoading: prepareLoading, data, } = useSimulateContract({
address,
chainId,
abi: SMART_INVOICE_UPDATABLE_ABI,
functionName: 'addMilestones',
args: [parsedMilestones, details],
query: {
enabled: _.every(parsedMilestones, m => m > BigInt(0)) && !!details,
},
});
const [waitingForTx, setWaitingForTx] = useState(false);
const { writeContractAsync, isPending: isLoading, error: writeError, } = useWriteContract({
mutation: {
onSuccess: async (hash) => {
setWaitingForTx(true);
toast.loading(TOASTS.useAddMilestone.waitingForTx);
const receipt = await publicClient?.waitForTransactionReceipt({ hash });
toast.loading(TOASTS.useAddMilestone.waitingForIndex);
if (receipt && publicClient) {
await waitForSubgraphSync(publicClient.chain.id, receipt.blockNumber);
}
setWaitingForTx(false);
onTxSuccess?.();
},
onError: (error) => errorToastHandler('useAddMilestones', error, toast),
},
});
const writeAsync = useCallback(async () => {
try {
if (!data) {
throw new Error('simulation data is not available');
}
return writeContractAsync(data.request);
}
catch (error) {
errorToastHandler('useAddMilestones', error, toast);
return undefined;
}
}, [writeContractAsync, data]);
return {
writeAsync,
isLoading: isLoading || waitingForTx || prepareLoading || detailsLoading,
prepareError,
writeError,
};
};