Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add discourse #75

Merged
merged 12 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/components/pages/attestations/DiscourseStepFour.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useState } from 'react';
import {
Box,
Button,
CircularProgress,
Stack,
Typography,
} from '@mui/material';
import { FaLink } from 'react-icons/fa';
import { useNavigate } from 'react-router-dom';
import { Address } from 'viem';
import { useAccount } from 'wagmi';

import { AttestPayload } from '../../../interfaces';
import EASService from '../../../services/eas.service';
import useSnackbarStore from '../../../store/useSnackbarStore';
import { contracts } from '../../../utils/contracts/eas/contracts';
import { useSigner } from '../../../utils/eas-wagmi-utils';

interface DiscourseStepFourProps {
attestedSignutare: AttestPayload | null;
}
Comment on lines +20 to +22
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in prop name 'attestedSignutare'.

The prop name contains a typo which should be corrected to maintain code quality.

 interface DiscourseStepFourProps {
-  attestedSignutare: AttestPayload | null;
+  attestedSignature: AttestPayload | null;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface DiscourseStepFourProps {
attestedSignutare: AttestPayload | null;
}
interface DiscourseStepFourProps {
attestedSignature: AttestPayload | null;
}


const DiscourseStepFour: React.FC<DiscourseStepFourProps> = ({
attestedSignutare,
}) => {
const { showSnackbar } = useSnackbarStore();
const navigate = useNavigate();
const signer = useSigner();
const { chainId } = useAccount();
const [isLoading, setIsLoading] = useState<boolean>(false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implement proper cleanup for loading state.

The loading state should be cleaned up if the component unmounts during the attestation process.

-const [isLoading, setIsLoading] = useState<boolean>(false);
+const [isLoading, setIsLoading] = useState<boolean>(false);
+const mounted = useRef(true);
+
+useEffect(() => {
+  return () => {
+    mounted.current = false;
+  };
+}, []);

 const handleAttestByDelegation = async () => {
   // ... existing code ...
   try {
     await easService.attestByDelegation(attestedSignutare);
     showSnackbar('Attestation successfully completed.', {
       severity: 'success',
     });
     navigate('/identifiers');
   } catch (error) {
     console.error('Error attesting identifier:', error);
     showSnackbar('Failed to complete the attestation. Please try again.', {
       severity: 'error',
     });
   } finally {
-    setIsLoading(false);
+    if (mounted.current) {
+      setIsLoading(false);
+    }
   }
 };

Also applies to: 47-61


const easContractAddress = contracts.find(
(contract) => contract.chainId === chainId
)?.easContractAddress;
Comment on lines +33 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for chainId existence.

The current implementation doesn't handle the case where no matching contract is found for the chainId.

 const easContractAddress = contracts.find(
   (contract) => contract.chainId === chainId
 )?.easContractAddress;
+
+if (!easContractAddress) {
+  throw new Error(`No contract found for chain ID: ${chainId}`);
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const easContractAddress = contracts.find(
(contract) => contract.chainId === chainId
)?.easContractAddress;
const easContractAddress = contracts.find(
(contract) => contract.chainId === chainId
)?.easContractAddress;
if (!easContractAddress) {
throw new Error(`No contract found for chain ID: ${chainId}`);
}


const easService = signer
? new EASService(easContractAddress as Address, signer)
: null;

const handleAttestByDelegation = async () => {
if (!easService) {
throw new Error('EAS service not initialized');
}
if (!attestedSignutare) throw new Error('No attested signature provided');

setIsLoading(true);
try {
await easService.attestByDelegation(attestedSignutare);
showSnackbar('Attestation successfully completed.', {
severity: 'success',
});
navigate('/identifiers');
} catch (error) {
console.error('Error attesting identifier:', error);
showSnackbar('Failed to complete the attestation. Please try again.', {
severity: 'error',
});
} finally {
setIsLoading(false);
}
};

return (
<Stack
spacing={3}
sx={{
textAlign: 'center',
py: 12,
px: 2,
}}
>
<Typography variant="h5" fontWeight="bold">
Finalize Delegated Attestation
</Typography>
<Typography>
To complete the process, you will be asked to sign a message with your
wallet, confirming ownership of the provided address.
</Typography>
<Box>
<Button
variant="contained"
startIcon={
isLoading ? (
<CircularProgress color="inherit" size={20} />
) : (
<FaLink />
)
}
sx={{ mt: 2, px: 4 }}
onClick={handleAttestByDelegation}
disabled={isLoading}
>
{isLoading ? 'Processing...' : 'Sign Delegated Attestation'}
</Button>
</Box>
<Typography variant="caption">
You need to pay some <b>gas</b> to complete the process.
</Typography>
</Stack>
);
};

export default DiscourseStepFour;
83 changes: 83 additions & 0 deletions src/components/pages/attestations/DiscourseStepOne.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Box, Button, Stack, Typography } from '@mui/material';
import { jwtDecode } from 'jwt-decode';
import { FaDiscourse } from 'react-icons/fa';

import { useGenerateDiscourseVerificationTokenMutation } from '../../../services/api/eas/query';
import useSnackbarStore from '../../../store/useSnackbarStore';

interface DiscourseStepOneProps {
handleNextStep: () => void;
}

const DiscourseStepOne: React.FC<DiscourseStepOneProps> = ({
handleNextStep,
}) => {
const { showSnackbar } = useSnackbarStore();
const { mutate: mutateGenerateDiscourseVerificationToken, isPending } =
useGenerateDiscourseVerificationTokenMutation();

const handleGenerateDiscourseVerificationToken = async () => {
const siweJwt = localStorage.getItem('OCI_TOKEN') as string;

mutateGenerateDiscourseVerificationToken(
{
siweJwt,
},
{
onSuccess: (response) => {
const { data } = response;

localStorage.setItem(
'DISCOURSE_VERIFICATION_TOKEN',
data.verificationJwt
);
Comment on lines +30 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider secure token storage alternatives.

Storing sensitive tokens in localStorage poses security risks as it's vulnerable to XSS attacks. Consider using more secure alternatives:

  1. Use session storage for temporary tokens
  2. Implement token encryption before storage
  3. Store only non-sensitive parts of the token

Also applies to: 35-36


const { code } = jwtDecode(data.verificationJwt) as { code: string };
localStorage.setItem('DISCOURSE_VERIFICATION_CODE', code);

handleNextStep();
},
onError: (error) => {
console.error('Failed to generate token:', error);
showSnackbar(
'Failed to generate verification token. Please try again.',
{
severity: 'error',
}
);
},
}
);
};

return (
<Stack
spacing={2}
sx={{
textAlign: 'center',
py: 12,
}}
>
<Typography variant="h5" fontWeight="bold">
Let&rsquo;s get started!
</Typography>
<Typography variant="body2">
To attest your Discourse account, you need to generate a token.
</Typography>
<Box sx={{ display: 'block' }}>
<Button
variant="contained"
startIcon={<FaDiscourse />}
disabled={isPending}
onClick={handleGenerateDiscourseVerificationToken}
aria-busy={isPending}
aria-live="polite"
>
{isPending ? 'Generating token...' : 'Generate token'}
</Button>
</Box>
</Stack>
);
};

export default DiscourseStepOne;
96 changes: 96 additions & 0 deletions src/components/pages/attestations/DiscourseStepThree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React from 'react';
import {
Box,
Button,
CircularProgress,
Stack,
Typography,
} from '@mui/material';
import { FaLink } from 'react-icons/fa6';
import { useAccount } from 'wagmi';

import { Provider } from '../../../enums';
import { AttestPayload } from '../../../interfaces';
import { useLinkIdentifierMutation } from '../../../services/api/eas/query';
import { capitalize } from '../../../utils/helper';

interface DiscourseStepThreeProps {
provider: Provider | undefined;
handlePrepareAttestation: (payload: AttestPayload) => void;
}

const DiscourseStepThree: React.FC<DiscourseStepThreeProps> = ({
provider,
handlePrepareAttestation,
}) => {
const { chainId } = useAccount();
const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
chainId as number
);
Comment on lines +27 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid unsafe type assertion for chainId.

The current implementation assumes chainId is always a number, which might cause runtime errors.

-  const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
-    chainId as number
-  );
+  const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
+    chainId ?? 1 // Provide a fallback chain ID or handle the undefined case
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
chainId as number
);
const { mutate: mutateIdentifier, isPending } = useLinkIdentifierMutation(
chainId ?? 1 // Provide a fallback chain ID or handle the undefined case
);


const handleGenerateSignedDelegation = async () => {
const siweJwt = localStorage.getItem('OCI_TOKEN');
if (!siweJwt || !provider) return;

const anyJwt = localStorage.getItem('DISCOURSE_JWT') as string;

mutateIdentifier(
{
siweJwt,
anyJwt,
},
{
onSuccess: (response) => {
const { data } = response;
handlePrepareAttestation(data);
},
onError: (error) => {
console.error(error);
},
}
Comment on lines +47 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling with user feedback.

The error handler only logs to console without informing the user about the failure.

 onError: (error) => {
   console.error(error);
+  showSnackbar('Failed to generate signed delegation. Please try again.', {
+    severity: 'error',
+  });
 },

Committable suggestion skipped: line range outside the PR's diff.

);
};

if (!provider) {
return null;
}

return (
<Stack
spacing={3}
sx={{
textAlign: 'center',
py: 12,
px: 2,
}}
>
<Typography variant="h5" fontWeight="bold">
Connect Your {capitalize(provider)} Account to Your Wallet
</Typography>
<Typography>
To proceed, please verify your account by linking it to your wallet
address. This step ensures your {capitalize(provider)} account is
securely associated with your wallet.
</Typography>
<Box>
<Button
variant="contained"
startIcon={
isPending ? (
<CircularProgress color="inherit" size={20} />
) : (
<FaLink />
)
}
sx={{ mt: 2, px: 4 }}
onClick={handleGenerateSignedDelegation}
disabled={isPending}
>
{isPending ? 'Processing...' : 'Get Signed Delegated Attestation'}
</Button>
</Box>
</Stack>
);
};

export default DiscourseStepThree;
Loading
Loading