Skip to content

Commit

Permalink
Merge remote-tracking branch 'clearcontracts/main' into test
Browse files Browse the repository at this point in the history
  • Loading branch information
mesudip committed Nov 19, 2024
2 parents fd793e1 + 1fd2e12 commit ce528f5
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 52 deletions.
6 changes: 3 additions & 3 deletions src/components/buttons/beginVoteButton.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { useState } from 'react';
import Button from '@mui/material/Button';
import toast from 'react-hot-toast';

import { startVoting } from '@/lib/helpers/startVoting';

interface Props {
pollId: string | string[] | undefined;
isSubmitting: boolean;
setIsSubmitting: (value: boolean) => void;
}

/**
* A button for workshop coordinators to open voting for a poll
* @returns Begin Voting Button
*/
export function BeginVoteButton(props: Props): JSX.Element {
const { pollId, isSubmitting, setIsSubmitting } = props;
const { pollId } = props;
const [isSubmitting, setIsSubmitting] = useState(false);

async function handleBeginVote(): Promise<void> {
if (typeof pollId !== 'string') {
Expand Down
11 changes: 6 additions & 5 deletions src/components/buttons/deletePollButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import { useRouter } from 'next/router';
import { DeleteRounded } from '@mui/icons-material';
import Button from '@mui/material/Button';
Expand All @@ -9,16 +10,16 @@ import { archivePoll } from '@/lib/helpers/archivePoll';

interface Props {
pollId: string | string[] | undefined;
isSubmitting: boolean;
setIsSubmitting: (value: boolean) => void;
}

/**
* A button for workshop coordinators to open voting for a poll
* @returns Begin Voting Button
* A button for workshop coordinators to delete a poll
* @param props - Poll ID
* @returns Delete poll Button
*/
export function DeletePollButton(props: Props): JSX.Element {
const { pollId, isSubmitting, setIsSubmitting } = props;
const { pollId } = props;
const [isSubmitting, setIsSubmitting] = useState(false);

const session = useSession();
const router = useRouter();
Expand Down
8 changes: 3 additions & 5 deletions src/components/buttons/endVoteButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from 'react';
import Button from '@mui/material/Button';
import { useSession } from 'next-auth/react';
import toast from 'react-hot-toast';
Expand All @@ -7,8 +8,6 @@ import { getPollResults } from '@/lib/helpers/getPollResults';

interface Props {
pollId: string | string[] | undefined;
isSubmitting: boolean;
setIsSubmitting: (value: boolean) => void;
updatePollResults: (newPollResults: {
yes: {
name: string;
Expand All @@ -28,13 +27,12 @@ interface Props {
/**
* A button for workshop coordinators to end voting for a poll
* @param pollId - The pollId of the poll to end voting for
* @param isSubmitting - Whether the button is in a submitting state
* @param setIsSubmitting - Function to set the submitting state
* @param updatePollResults - Function to update the poll results after voting ends
* @returns End Voting Button
*/
export function EndVoteButton(props: Props): JSX.Element {
const { pollId, isSubmitting, setIsSubmitting, updatePollResults } = props;
const { pollId, updatePollResults } = props;
const [isSubmitting, setIsSubmitting] = useState(false);

const session = useSession();

Expand Down
54 changes: 35 additions & 19 deletions src/components/buttons/putVotesOnChainButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Button from '@mui/material/Button';
import { useSession } from 'next-auth/react';
import toast from 'react-hot-toast';

import { awaitTxConfirm } from '@/lib/awaitTxConfirm';
import { addTxToPoll } from '@/lib/helpers/addTxToPoll';
import { addTxToPollTransactions } from '@/lib/helpers/addTxToPollTransactions';
import { addTxToPollVotes } from '@/lib/helpers/addTxToPollVotes';
Expand All @@ -13,17 +14,19 @@ interface Props {
pollId: string | string[] | undefined;
isSubmitting: boolean;
setIsSubmitting: (value: boolean) => void;
setIsTxUploading: (value: boolean) => void;
}

/**
* A button for workshop coordinators to post poll votes on chain
* @param pollId - The pollId of the poll to end voting for
* @param isSubmitting - Whether the button is in a submitting state
* @param setIsSubmitting - Function to set the submitting state
* @param setIsTxUploading - Function to set the transaction submitting state
* @returns Put votes on-chain Button
*/
export function PutVotesOnChainButton(props: Props): JSX.Element {
const { pollId, isSubmitting, setIsSubmitting } = props;
const { pollId, isSubmitting, setIsSubmitting, setIsTxUploading } = props;

const session = useSession();

Expand All @@ -42,18 +45,19 @@ export function PutVotesOnChainButton(props: Props): JSX.Element {
// Keep trying until the transaction is successful
while (!success) {
// Post votes on chain
const txHash = await postVotesOnChain(metadata.metadata);
if (txHash) {
const txResult = await postVotesOnChain(metadata.metadata);
setIsTxUploading(true);
if (txResult) {
// TODO: Figure out how to handle errors in addTxToPollTransactions and addTxToPollVotes since the data is already on-chain
success = true;
// Add txHash to poll
const addTxResponse = await addTxToPollTransactions(
pollId,
txHash.submitTxId,
txResult.submitTxId,
);
if (addTxResponse.pollTransactionId === '-1') {
toast.error(addTxResponse.message);
break;
break; // TODO: Do we really want to break here?
}
// Add txHash to poll votes
const addTxToVotesResponse = await addTxToPollVotes(
Expand All @@ -63,8 +67,16 @@ export function PutVotesOnChainButton(props: Props): JSX.Element {
);
if (!addTxToVotesResponse.succeeded) {
toast.error(addTxToVotesResponse.message);
break;
break; // TODO: Do we really want to break here?
}
await awaitTxConfirm(txResult.submitTxId).then(() =>
setIsTxUploading(false),
);
} else {
toast.error(
'Error posting this TX on-chain. Please try signing this TX again. If the issue persists, refresh the app and re-click the upload votes on-chain button.',
);
break;
}
}
}
Expand All @@ -78,18 +90,20 @@ export function PutVotesOnChainButton(props: Props): JSX.Element {
while (!summaryTxSuccess) {
const response = await getSummaryTxMetadata(pollId);
if (response.metadata) {
const txHash = await postVotesOnChain(response.metadata);
if (txHash) {
const txResult = await postVotesOnChain(response.metadata);
setIsTxUploading(true);
if (txResult) {
// TODO: Figure out how to handle errors in addTxToPoll since the data is already on-chain
summaryTxSuccess = true;
// Add txHash to poll
const addTxResponse = await addTxToPoll(pollId, txHash.submitTxId);
const addTxResponse = await addTxToPoll(pollId, txResult.submitTxId);
if (!addTxResponse.success) {
toast.error(addTxResponse.message);
break;
} else {
toast.success('Votes successfully uploaded on-chain');
}
await awaitTxConfirm(txResult.submitTxId).then(() =>
setIsTxUploading(false),
);
}
} else {
toast.error(response.message);
Expand All @@ -101,14 +115,16 @@ export function PutVotesOnChainButton(props: Props): JSX.Element {

if (session.data?.user.isCoordinator) {
return (
<Button
onClick={handleClick}
variant="contained"
disabled={isSubmitting}
data-testid="put-votes-onchain-button"
>
Upload votes on-chain
</Button>
<>
<Button
onClick={handleClick}
variant="contained"
disabled={isSubmitting}
data-testid="put-votes-onchain-button"
>
Upload votes on-chain
</Button>
</>
);
} else {
return <></>;
Expand Down
7 changes: 2 additions & 5 deletions src/components/buttons/voteOnPollButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,20 @@ import { getPollVote } from '@/lib/helpers/getPollVote';
interface Props {
pollName: string;
pollId: string;
disabled: boolean;
setDisabled: (value: boolean) => void;
isActiveVoter: boolean;
}

/**
* Yes, No, Abstain buttons to vote on a poll
* @param pollName - The name of the poll
* @param pollId - The ID of the poll
* @param disabled - Whether the buttons are disabled
* @param setDisabled - Function to set the disabled state
* @param isActiveVoter - Whether the user is the active voter
* @returns Vote on Poll Buttons
*/
export function VoteOnPollButtons(props: Props): JSX.Element {
const { pollName, pollId, disabled, setDisabled, isActiveVoter } = props;
const { pollName, pollId, isActiveVoter } = props;
const [vote, setVote] = useState('');
const [disabled, setDisabled] = useState(false);

const session = useSession();
const theme = useTheme();
Expand Down
54 changes: 54 additions & 0 deletions src/components/txPopups/txPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useTheme } from '@mui/material';
import Alert from '@mui/material/Alert';
import AlertTitle from '@mui/material/AlertTitle';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import Typography from '@mui/material/Typography';

interface Props {
title: string;
message: string;
}

/**
* Transaction in progress popup
* @param title - Title of the popup
* @param message - Message of the popup
* @returns Popup for when a transaction is in progress
*/
export function TxPopup(props: Props): JSX.Element {
const { title, message } = props;
const theme = useTheme();

return (
<Box display="flex" alignContent="center">
<Alert
elevation={6}
variant="filled"
severity="warning"
iconMapping={{
warning: (
<Box
alignItems="center"
justifyContent="center"
display="flex"
minWidth="40px"
>
<CircularProgress />
</Box>
),
}}
>
<AlertTitle color={theme.palette.secondary.contrastText}>
{title}
</AlertTitle>
<Typography
variant="subtitle2"
color={theme.palette.secondary.contrastText}
>
{message}
</Typography>
</Alert>
</Box>
);
}
48 changes: 48 additions & 0 deletions src/lib/awaitTxConfirm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { isTransactionConfirmed } from '@claritydao/clarity-backend';
import * as Sentry from '@sentry/nextjs';
import toast from 'react-hot-toast';

import { buildClarityBackendReq } from '@/lib/buildClarityBackendReq';

/**
* Monitors a transaction until it is confirmed
* @param txId - The transaction ID to monitor
* @returns Boolean - True if the transaction is confirmed, false otherwise
*/
export async function awaitTxConfirm(txId: string): Promise<boolean> {
try {
const clarityBackendReq = await buildClarityBackendReq();
if (!clarityBackendReq) {
toast.error('Error monitoring transaction.');
return false;
}
return new Promise((resolve) => {
// timeout is just hardcoded to 300 seconds (5 minutes)
let elapsedTime = 0;
const intervalId = setInterval(async () => {
elapsedTime += 5000; // Increment elapsed time by 5 seconds (5000 ms)
// If the elapsed time exceeds or equals the max time, clear the interval and exit
if (elapsedTime >= 300000) {
// Clear the interval once the transaction is confirmed
clearInterval(intervalId);
resolve(false);
}

const isConfirmed = await isTransactionConfirmed(
clarityBackendReq.url,
txId,
);

if (isConfirmed) {
// Clear the interval once the transaction is confirmed
clearInterval(intervalId);
resolve(true);
}
}, 5000); // Run every 5 seconds
});
} catch (error) {
Sentry.captureException(error);
toast.error('Error monitoring transaction.');
return false;
}
}
3 changes: 3 additions & 0 deletions src/lib/postVotesOnChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export async function postVotesOnChain(
},
},
);
// Logging the TX hash just in case there is an issue saving the TX hash to the DB.
// We can at least look it up in the logs and then manually add it.
console.log('txHash', txHash);
return txHash;
} catch (error) {
Sentry.captureException(error);
Expand Down
Loading

0 comments on commit ce528f5

Please sign in to comment.