-
Notifications
You must be signed in to change notification settings - Fork 0
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
add discourse #75
Changes from all commits
a15df17
8a882da
ea531f3
4fef466
c8f2c30
14bd160
6a235c7
aaa0b37
79e98ff
0c6c223
bd51d19
9b1f2ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
const DiscourseStepFour: React.FC<DiscourseStepFourProps> = ({ | ||||||||||||||||||||||
attestedSignutare, | ||||||||||||||||||||||
}) => { | ||||||||||||||||||||||
const { showSnackbar } = useSnackbarStore(); | ||||||||||||||||||||||
const navigate = useNavigate(); | ||||||||||||||||||||||
const signer = useSigner(); | ||||||||||||||||||||||
const { chainId } = useAccount(); | ||||||||||||||||||||||
const [isLoading, setIsLoading] = useState<boolean>(false); | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
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; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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:
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’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; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Suggested change
|
||||||||||||||
|
||||||||||||||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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',
+ });
},
|
||||||||||||||
); | ||||||||||||||
}; | ||||||||||||||
|
||||||||||||||
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix typo in prop name 'attestedSignutare'.
The prop name contains a typo which should be corrected to maintain code quality.
📝 Committable suggestion