-
-
Notifications
You must be signed in to change notification settings - Fork 33
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 a fix for conflicting Stellar transactions #5031
base: develop
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis pull request introduces asynchronous QR code generation within the donation view. A new state variable ( Changes
Sequence Diagram(s)sequenceDiagram
participant C as QRDonationCardContent
participant E as useEffect
participant F as generateStellarPaymentQRCode
participant S as State (qrData)
participant I as ImageComponent
C->>E: On mount / data change
E->>F: Call generateStellarPaymentQRCode(params)
F-->>E: Return QR code data URL
E->>S: Update qrData state
C->>I: Render with new dataUrl (qrData)
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
src/hooks/useQRCodeDonation.ts (1)
113-116
:⚠️ Potential issueImprove error handling to prevent information leakage.
The current error handling could expose sensitive information by directly throwing the error message.
Apply this diff to improve error handling:
} catch (error: any) { - console.error('Error creating draft donation', error.message); - throw error.message; + console.error('Error creating draft donation:', error); + throw new Error('Failed to create draft donation. Please try again.'); }
🧹 Nitpick comments (2)
src/hooks/useQRCodeDonation.ts (1)
93-111
: Add cleanup mechanism for local storage.The local storage could grow indefinitely as new draft donations are added without removing old ones.
Consider adding a cleanup mechanism to remove expired draft donations from local storage. Here's a suggested implementation:
+ const cleanupOldDraftDonations = (storageData: Record<string, any>) => { + const now = new Date().getTime(); + return Object.entries(storageData).reduce((acc, [key, value]) => { + if (value.expiresAt && new Date(value.expiresAt).getTime() > now) { + acc[key] = value; + } + return acc; + }, {} as Record<string, any>); + }; + const localStorageItem = localStorage.getItem( StorageLabel.DRAFT_DONATIONS, ); if (localStorageItem) { const parsedLocalStorageItem = JSON.parse(localStorageItem); + const cleanedStorageItem = cleanupOldDraftDonations(parsedLocalStorageItem); parsedLocalStorageItem[walletAddress] = createDraftDonation; localStorage.setItem( StorageLabel.DRAFT_DONATIONS, - JSON.stringify(parsedLocalStorageItem), + JSON.stringify(cleanedStorageItem), );src/components/views/donate/OneTime/SelectTokenModal/QRCodeDonation/QRDonationCardContent.tsx (1)
33-78
: Improve accessibility of the ImageComponent.The component needs better accessibility support for screen readers.
Apply this diff to improve accessibility:
const ImageComponent = ({ dataUrl, isExpired, + error, + isLoading, }: { dataUrl: string; isExpired?: boolean; + error?: string | null; + isLoading?: boolean; }) => { const { formatMessage } = useIntl(); + if (isLoading) { + return ( + <Flex + $justifyContent='center' + $alignItems='center' + style={{ marginBlock: '2rem' }} + role="status" + aria-label={formatMessage({ id: 'label.generating_qr_code' })} + > + <Spinner /> + </Flex> + ); + } return dataUrl ? ( <ImageWrapper> {isExpired && ( - <Overlay> + <Overlay role="alert" aria-live="polite"> <Flex $justifyContent='center' $alignItems='center'> <InlineToast type={EToastType.Info} message={formatMessage({ id: 'label.qr_code_expired', })} /> </Flex> </Overlay> )} <Image src={dataUrl} alt='QR Code' + aria-label={formatMessage({ id: 'label.qr_code_for_payment' })} width={200} height={200} unoptimized /> </ImageWrapper> ) : ( <Flex $justifyContent='center' $alignItems='center' style={{ marginBlock: '2rem' }} + role="alert" + aria-live="polite" > <InlineToast type={EToastType.Error} message={formatMessage({ - id: 'label.qr_code_error', + id: error ? 'label.qr_code_error' : 'label.qr_code_not_available', })} /> </Flex> ); };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/views/donate/OneTime/SelectTokenModal/QRCodeDonation/QRDonationCardContent.tsx
(4 hunks)src/hooks/useQRCodeDonation.ts
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
export const generateStellarPaymentQRCode = async ( | ||
toWalletAddress: string, | ||
amount: number, | ||
memo = '', | ||
draftDonationId?: number, | ||
) => { | ||
const formattedAddress = toWalletAddress.toUpperCase(); | ||
|
||
const paymentData = `stellar:${formattedAddress}?amount=${amount}&memo=${memo || draftDonationId}`; | ||
|
||
try { | ||
// Generate the QR code as a data URL | ||
const qrCodeDataURL = await QRCode.toDataURL(paymentData); | ||
return qrCodeDataURL; | ||
} catch (error) { | ||
console.error('Error generating QR code:', error); | ||
throw error; | ||
} | ||
}; |
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.
Add input validation and sanitization.
The function needs validation for its parameters to prevent invalid QR codes and potential security issues.
Apply this diff to add input validation and sanitization:
export const generateStellarPaymentQRCode = async (
toWalletAddress: string,
amount: number,
memo = '',
draftDonationId?: number,
) => {
+ if (!toWalletAddress?.trim()) {
+ throw new Error('Wallet address is required');
+ }
+ if (amount <= 0) {
+ throw new Error('Amount must be greater than 0');
+ }
+
const formattedAddress = toWalletAddress.toUpperCase();
+ const sanitizedMemo = (memo || draftDonationId || '').toString()
+ .replace(/[<>'"&]/g, ''); // Basic XSS prevention
- const paymentData = `stellar:${formattedAddress}?amount=${amount}&memo=${memo || draftDonationId}`;
+ const paymentData = `stellar:${formattedAddress}?amount=${amount}&memo=${sanitizedMemo}`;
try {
// Generate the QR code as a data URL
const qrCodeDataURL = await QRCode.toDataURL(paymentData);
return qrCodeDataURL;
} catch (error) {
- console.error('Error generating QR code:', error);
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error('Error generating QR code:', errorMessage);
throw error;
}
};
📝 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.
export const generateStellarPaymentQRCode = async ( | |
toWalletAddress: string, | |
amount: number, | |
memo = '', | |
draftDonationId?: number, | |
) => { | |
const formattedAddress = toWalletAddress.toUpperCase(); | |
const paymentData = `stellar:${formattedAddress}?amount=${amount}&memo=${memo || draftDonationId}`; | |
try { | |
// Generate the QR code as a data URL | |
const qrCodeDataURL = await QRCode.toDataURL(paymentData); | |
return qrCodeDataURL; | |
} catch (error) { | |
console.error('Error generating QR code:', error); | |
throw error; | |
} | |
}; | |
export const generateStellarPaymentQRCode = async ( | |
toWalletAddress: string, | |
amount: number, | |
memo = '', | |
draftDonationId?: number, | |
) => { | |
if (!toWalletAddress?.trim()) { | |
throw new Error('Wallet address is required'); | |
} | |
if (amount <= 0) { | |
throw new Error('Amount must be greater than 0'); | |
} | |
const formattedAddress = toWalletAddress.toUpperCase(); | |
const sanitizedMemo = (memo || draftDonationId || '').toString().replace(/[<>'"&]/g, ''); // Basic XSS prevention | |
const paymentData = `stellar:${formattedAddress}?amount=${amount}&memo=${sanitizedMemo}`; | |
try { | |
// Generate the QR code as a data URL | |
const qrCodeDataURL = await QRCode.toDataURL(paymentData); | |
return qrCodeDataURL; | |
} catch (error) { | |
const errorMessage = error instanceof Error ? error.message : 'Unknown error'; | |
console.error('Error generating QR code:', errorMessage); | |
throw error; | |
} | |
}; |
const [qrData, setQRData] = React.useState<string | null>(null); | ||
|
||
useEffect(() => { | ||
const generateQRImage = async () => { | ||
const qrImg = await generateStellarPaymentQRCode( | ||
projectAddress?.address ?? '', | ||
draftDonationData?.amount ?? 0, | ||
projectAddress?.memo ?? '', | ||
draftDonationData?.id, | ||
); | ||
|
||
setQRData(qrImg); | ||
}; | ||
|
||
generateQRImage(); | ||
}, [projectAddress, draftDonationData]); |
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.
Add error handling and prevent race conditions in QR code generation.
The current implementation lacks error handling and could lead to race conditions with stale QR codes.
Apply this diff to improve the implementation:
- const [qrData, setQRData] = React.useState<string | null>(null);
+ const [qrData, setQRData] = React.useState<{ url: string | null; error: string | null }>({
+ url: null,
+ error: null
+ });
+ const [isGeneratingQR, setIsGeneratingQR] = React.useState(false);
useEffect(() => {
+ let mounted = true;
const generateQRImage = async () => {
+ if (!projectAddress?.address || !draftDonationData?.amount) {
+ return;
+ }
+ setIsGeneratingQR(true);
try {
const qrImg = await generateStellarPaymentQRCode(
projectAddress?.address ?? '',
draftDonationData?.amount ?? 0,
projectAddress?.memo ?? '',
draftDonationData?.id,
);
- setQRData(qrImg);
+ if (mounted) {
+ setQRData({ url: qrImg, error: null });
+ }
} catch (error) {
+ console.error('Failed to generate QR code:', error);
+ if (mounted) {
+ setQRData({ url: null, error: 'Failed to generate QR code' });
+ }
+ } finally {
+ if (mounted) {
+ setIsGeneratingQR(false);
+ }
}
};
generateQRImage();
+ return () => {
+ mounted = false;
+ };
}, [projectAddress, draftDonationData]);
Then update the ImageComponent usage:
<ImageComponent
- dataUrl={qrData ?? ''}
+ dataUrl={qrData.url ?? ''}
isExpired={
!!qrDonationStatus &&
['expired', 'failed'].includes(qrDonationStatus)
}
+ error={qrData.error}
+ isLoading={isGeneratingQR}
/>
📝 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.
const [qrData, setQRData] = React.useState<string | null>(null); | |
useEffect(() => { | |
const generateQRImage = async () => { | |
const qrImg = await generateStellarPaymentQRCode( | |
projectAddress?.address ?? '', | |
draftDonationData?.amount ?? 0, | |
projectAddress?.memo ?? '', | |
draftDonationData?.id, | |
); | |
setQRData(qrImg); | |
}; | |
generateQRImage(); | |
}, [projectAddress, draftDonationData]); | |
const [qrData, setQRData] = React.useState<{ url: string | null; error: string | null }>({ | |
url: null, | |
error: null | |
}); | |
const [isGeneratingQR, setIsGeneratingQR] = React.useState(false); | |
useEffect(() => { | |
let mounted = true; | |
const generateQRImage = async () => { | |
if (!projectAddress?.address || !draftDonationData?.amount) { | |
return; | |
} | |
setIsGeneratingQR(true); | |
try { | |
const qrImg = await generateStellarPaymentQRCode( | |
projectAddress?.address ?? '', | |
draftDonationData?.amount ?? 0, | |
projectAddress?.memo ?? '', | |
draftDonationData?.id, | |
); | |
if (mounted) { | |
setQRData({ url: qrImg, error: null }); | |
} | |
} catch (error) { | |
console.error('Failed to generate QR code:', error); | |
if (mounted) { | |
setQRData({ url: null, error: 'Failed to generate QR code' }); | |
} | |
} finally { | |
if (mounted) { | |
setIsGeneratingQR(false); | |
} | |
} | |
}; | |
generateQRImage(); | |
return () => { | |
mounted = false; | |
}; | |
}, [projectAddress, draftDonationData]); | |
/* ... other component code ... */ | |
<ImageComponent | |
dataUrl={qrData.url ?? ''} | |
isExpired={ | |
!!qrDonationStatus && | |
['expired', 'failed'].includes(qrDonationStatus) | |
} | |
error={qrData.error} | |
isLoading={isGeneratingQR} | |
/> |
Summary by CodeRabbit
New Features
Refactor