Continuation of Onramper widget (#605): testing and QA - #644
Conversation
…nramper widget (#605) * fix: Prevent Uniswap widget crashes on 100% price impact trades * updated patch_reference.md * separate concerns * Revert "updated patch_reference.md" This reverts commit ad8846f. * Add clean uniswap patch for 100% price impact fix * Add clean uniswap patch for price impact fix * fix: handle uniswap widget error * Update gitignore * updated gitignore file * updates suggest by yhy the reviewer * Update .gitignore Co-authored-by: Lewis B <lewis@ikigaistudios.eu> * feat: implement Buy G$ page with progress bar and Onramper widget * remove: removed fallback on posthog * chore: update localization catalogs * rm: Updated gitignore file * fix: fixing critical bugs and standards * rm: remove falback for posthog * fix: feedback fix on security, performance, functionality and design * fix: security and functionality fix * fix: address all Korbit AI review feedback for Buy G$ feature * fix: connect widget events to progress bar and fix swap lock bug * Update src/components/CustomGdOnramperWidget/CustomOnramper.tsx Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix: show wallet connection placeholder on Buy G$ page * fix: update BuyGD to use new wallet connection system * refactor: use GdOnramperWidget from good-design package --------- Co-authored-by: Lewis B <lewis@ikigaistudios.eu> Co-authored-by: LewisB <lewis@gooddollar.org> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Deploying goodprotocolui with
|
| Latest commit: |
dd41aa5
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://b20d2272.goodprotocolui.pages.dev |
| Branch Preview URL: | https://draft-buygd-testing.goodprotocolui.pages.dev |
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
Fixed security issues:
-
browserify-sign (link)
-
pbkdf2 (link)
-
svgo (link)
-
underscore (link)
-
The new BuyProgressBar component hardcodes English step labels; consider wiring this through your existing i18n macros so the onramp flow is consistently localized.
-
BuyGD.css uses very broad selectors (e.g. .progress-bar, .stepper, [class*="progress"]) that will hide progress UI globally; it would be safer to scope these rules to the Buy G$ page or the specific Onramper container to avoid unintended side effects.
-
package.json now points @gooddollar/good-design and @gooddollar/web3sdk-v2 to local file: temp-packages; please confirm this is intended for the main branch and not just for local testing, or swap back to the published versions before merging.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new BuyProgressBar component hardcodes English step labels; consider wiring this through your existing i18n macros so the onramp flow is consistently localized.
- BuyGD.css uses very broad selectors (e.g. .progress-bar, .stepper, [class*="progress"]) that will hide progress UI globally; it would be safer to scope these rules to the Buy G$ page or the specific Onramper container to avoid unintended side effects.
- package.json now points @gooddollar/good-design and @gooddollar/web3sdk-v2 to local file: temp-packages; please confirm this is intended for the main branch and not just for local testing, or swap back to the published versions before merging.
## Individual Comments
### Comment 1
<location path="src/pages/gd/BuyGD/BuyGD.css" line_range="21-27" />
<code_context>
+}
+
+/* Alternative approach - hide progress elements by common selectors */
+.progress-bar,
+.stepper,
+.steps-container,
+[class*="progress"],
+[class*="stepper"],
+[class*="step"] {
+ display: none !important;
+}
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Global selectors for `.progress-bar` / `.stepper` etc. are likely to hide unrelated UI across the app.
These selectors apply `display: none !important;` to very generic class names and substring matches, which can unintentionally hide progress/stepper components elsewhere in the app or from third‑party libraries. Please scope these rules under a dedicated Onramper wrapper (e.g. a specific container id/class or iframe context) so they only affect the embedded widget.
</issue_to_address>
### Comment 2
<location path="src/components/BuyProgressBar/index.tsx" line_range="108-117" />
<code_context>
+ const getLineProps = (stepNumber: number, lineIndex: number) => {
</code_context>
<issue_to_address>
**suggestion:** The `stepNumber` parameter in `getLineProps` is unused and the line positioning relies on brittle magic percentages.
This makes the signature misleading and tightly couples the layout to exactly three steps, so any change to `steps` (length or spacing) will likely break line alignment. Please either compute line positions based on `steps.length` and the actual flex layout, or explicitly constrain/document a fixed steps structure, and remove the unused `stepNumber` parameter.
Suggested implementation:
```typescript
const getCircleProps = (status: string) => {
return circlePropsMap[status as keyof typeof circlePropsMap] || circlePropsMap.pending
}
/**
* Returns style props for the connecting line segments between steps.
*
* This implementation assumes a fixed three-step layout where:
* - lineIndex = 0 is the line between step 1 and 2
* - lineIndex = 1 is the line between step 2 and 3
*
* If the steps structure changes (length or spacing), this function
* must be updated accordingly or refactored to derive positions from
* the steps array and the flex layout.
*/
const getLineProps = (lineIndex: number) => {
// Line between step 1 and 2 (lineIndex = 0)
if (lineIndex === 0) {
if (currentStep === 1 && isLoading) {
// Animation state: "1 Blue with progress bar animation"
return {
bg: 'blue.500',
width: `${animatedWidth}%`,
transition: 'width 0.1s ease-out',
}
} else if (currentStep >= 2) {
```
1. Remove the `stepNumber` argument from every call site of `getLineProps` in `src/components/BuyProgressBar/index.tsx`, so calls become `getLineProps(lineIndex)` instead of `getLineProps(stepNumber, lineIndex)`.
2. If there is a `steps` array or dynamic steps layout elsewhere in the file, consider refactoring `getLineProps` to derive any non-animated widths/positions from `steps.length` and the flex container (for example, by using `flex="1"` or `width="100%"` on line containers) instead of hard-coded percentage widths.
3. If additional branches inside `getLineProps` rely on hard-coded percentages tied to specific step indexes, update their comments to match the documented three-step assumption or refactor them to use layout-based calculations as in point 2.
</issue_to_address>
### Comment 3
<location path="src/components/BuyProgressBar/index.tsx" line_range="66" />
<code_context>
+ return 'pending'
+ }
+
+ // Memoize circle props objects to avoid recreation on every render
+ const circlePropsMap = useMemo(
+ () => ({
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the progress bar by removing unnecessary memoization and bespoke status logic, deriving connector styles from step status, using flex-based layout, and optionally replacing JS-driven animation with CSS-only animation.
You can keep the same behavior while simplifying a few areas:
1. **Drop `useMemo` for `circlePropsMap`**
The object is static and cheap to recreate; memoization here adds indirection without benefit.
```ts
// Remove useMemo import and hook, and define once
const circlePropsMap = {
completed: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'blue.500',
},
active: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'blue.500',
},
loading: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'blue.500',
borderWidth: 3,
borderColor: 'blue.200',
animation: 'pulse 2s infinite',
},
pending: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'gray.300',
},
}
const getCircleProps = (status: StepStatus) =>
circlePropsMap[status] ?? circlePropsMap.pending
```
2. **Unify status model and remove special casing**
You can represent the step status with a single type and avoid the bespoke `stepNumber === 1` logic by expressing that in the generic rules:
```ts
type StepStatus = 'pending' | 'active' | 'loading' | 'completed'
const getStepStatus = (stepNumber: number): StepStatus => {
if (stepNumber < currentStep) return 'completed'
if (stepNumber === currentStep) return isLoading ? 'loading' : 'active'
return 'pending'
}
// If you need “step 1 is always blue”, encode it via styling instead of status branching:
const isFirstStep = stepNumber === 1
const circleProps = {
...getCircleProps(status),
bg: isFirstStep || status !== 'pending' ? 'blue.500' : 'gray.300',
}
```
This keeps the “step 1 always blue when not pending” behavior but simplifies the state logic.
3. **Simplify line state based on right-hand step status**
Instead of separate `lineIndex` and `currentStep` branches, derive line props from the status of the step it leads to:
```ts
const getLinePropsForStep = (toStepNumber: number): { bg: string; width: string } => {
const status = getStepStatus(toStepNumber)
if (status === 'loading') {
return {
bg: 'blue.500',
width: `${animatedWidth}%`,
}
}
if (status === 'completed' || status === 'active') {
return {
bg: 'blue.500',
width: '100%',
}
}
return {
bg: 'gray.300',
width: '100%',
}
}
// usage:
<Box height="100%" {...getLinePropsForStep(step.number + 1)} borderRadius="1px" />
```
This removes the need for `lineIndex` and makes the “state machine” easier to follow.
4. **Use flex instead of magic percentage positioning**
You can avoid the `33.33`/`16.67` calculations by making the connectors flex between circles:
```tsx
<HStack alignItems="center" justifyContent="space-between">
{steps.map((step, index) => {
const status = getStepStatus(step.number)
return (
<React.Fragment key={step.number}>
<Box alignItems="center">
{/* circle + label */}
</Box>
{index < steps.length - 1 && (
<Box flex={1} mx={2} height="2px" bg="gray.300">
<Box height="100%" {...getLinePropsForStep(step.number + 1)} borderRadius="1px" />
</Box>
)}
</React.Fragment>
)
})}
</HStack>
```
This keeps the same visual intent but removes manual `left`/`right` positioning.
5. **Optional: replace `setInterval` with CSS-only animation**
If acceptable, you can avoid interval management entirely and keep the loading bar animated via CSS:
```tsx
const getLinePropsForStep = (toStepNumber: number) => {
const status = getStepStatus(toStepNumber)
if (status === 'loading') {
return {
bg: 'blue.500',
width: '100%',
// assuming a keyframe like `@keyframes loadingBar { from { width: 0 } to { width: 100% } }`
animation: 'loadingBar 2s infinite',
}
}
// active/completed/pending same as before, no extra state
}
```
This removes `animatedWidth` state and `useEffect` while preserving the animated look.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| .progress-bar, | ||
| .stepper, | ||
| .steps-container, | ||
| [class*="progress"], | ||
| [class*="stepper"], | ||
| [class*="step"] { | ||
| display: none !important; |
There was a problem hiding this comment.
issue (bug_risk): Global selectors for .progress-bar / .stepper etc. are likely to hide unrelated UI across the app.
These selectors apply display: none !important; to very generic class names and substring matches, which can unintentionally hide progress/stepper components elsewhere in the app or from third‑party libraries. Please scope these rules under a dedicated Onramper wrapper (e.g. a specific container id/class or iframe context) so they only affect the embedded widget.
| const getLineProps = (stepNumber: number, lineIndex: number) => { | ||
| // Line between step 1 and 2 (lineIndex = 0) | ||
| if (lineIndex === 0) { | ||
| if (currentStep === 1 && isLoading) { | ||
| // Animation state: "1 Blue with progress bar animation" | ||
| return { | ||
| bg: 'blue.500', | ||
| width: `${animatedWidth}%`, | ||
| transition: 'width 0.1s ease-out', | ||
| } |
There was a problem hiding this comment.
suggestion: The stepNumber parameter in getLineProps is unused and the line positioning relies on brittle magic percentages.
This makes the signature misleading and tightly couples the layout to exactly three steps, so any change to steps (length or spacing) will likely break line alignment. Please either compute line positions based on steps.length and the actual flex layout, or explicitly constrain/document a fixed steps structure, and remove the unused stepNumber parameter.
Suggested implementation:
const getCircleProps = (status: string) => {
return circlePropsMap[status as keyof typeof circlePropsMap] || circlePropsMap.pending
}
/**
* Returns style props for the connecting line segments between steps.
*
* This implementation assumes a fixed three-step layout where:
* - lineIndex = 0 is the line between step 1 and 2
* - lineIndex = 1 is the line between step 2 and 3
*
* If the steps structure changes (length or spacing), this function
* must be updated accordingly or refactored to derive positions from
* the steps array and the flex layout.
*/
const getLineProps = (lineIndex: number) => {
// Line between step 1 and 2 (lineIndex = 0)
if (lineIndex === 0) {
if (currentStep === 1 && isLoading) {
// Animation state: "1 Blue with progress bar animation"
return {
bg: 'blue.500',
width: `${animatedWidth}%`,
transition: 'width 0.1s ease-out',
}
} else if (currentStep >= 2) {- Remove the
stepNumberargument from every call site ofgetLinePropsinsrc/components/BuyProgressBar/index.tsx, so calls becomegetLineProps(lineIndex)instead ofgetLineProps(stepNumber, lineIndex). - If there is a
stepsarray or dynamic steps layout elsewhere in the file, consider refactoringgetLinePropsto derive any non-animated widths/positions fromsteps.lengthand the flex container (for example, by usingflex="1"orwidth="100%"on line containers) instead of hard-coded percentage widths. - If additional branches inside
getLinePropsrely on hard-coded percentages tied to specific step indexes, update their comments to match the documented three-step assumption or refactor them to use layout-based calculations as in point 2.
| return 'pending' | ||
| } | ||
|
|
||
| // Memoize circle props objects to avoid recreation on every render |
There was a problem hiding this comment.
issue (complexity): Consider simplifying the progress bar by removing unnecessary memoization and bespoke status logic, deriving connector styles from step status, using flex-based layout, and optionally replacing JS-driven animation with CSS-only animation.
You can keep the same behavior while simplifying a few areas:
- Drop
useMemoforcirclePropsMap
The object is static and cheap to recreate; memoization here adds indirection without benefit.
// Remove useMemo import and hook, and define once
const circlePropsMap = {
completed: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'blue.500',
},
active: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'blue.500',
},
loading: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'blue.500',
borderWidth: 3,
borderColor: 'blue.200',
animation: 'pulse 2s infinite',
},
pending: {
size: '12',
mb: 2,
justifyContent: 'center',
alignItems: 'center',
bg: 'gray.300',
},
}
const getCircleProps = (status: StepStatus) =>
circlePropsMap[status] ?? circlePropsMap.pending- Unify status model and remove special casing
You can represent the step status with a single type and avoid the bespoke stepNumber === 1 logic by expressing that in the generic rules:
type StepStatus = 'pending' | 'active' | 'loading' | 'completed'
const getStepStatus = (stepNumber: number): StepStatus => {
if (stepNumber < currentStep) return 'completed'
if (stepNumber === currentStep) return isLoading ? 'loading' : 'active'
return 'pending'
}
// If you need “step 1 is always blue”, encode it via styling instead of status branching:
const isFirstStep = stepNumber === 1
const circleProps = {
...getCircleProps(status),
bg: isFirstStep || status !== 'pending' ? 'blue.500' : 'gray.300',
}This keeps the “step 1 always blue when not pending” behavior but simplifies the state logic.
- Simplify line state based on right-hand step status
Instead of separate lineIndex and currentStep branches, derive line props from the status of the step it leads to:
const getLinePropsForStep = (toStepNumber: number): { bg: string; width: string } => {
const status = getStepStatus(toStepNumber)
if (status === 'loading') {
return {
bg: 'blue.500',
width: `${animatedWidth}%`,
}
}
if (status === 'completed' || status === 'active') {
return {
bg: 'blue.500',
width: '100%',
}
}
return {
bg: 'gray.300',
width: '100%',
}
}
// usage:
<Box height="100%" {...getLinePropsForStep(step.number + 1)} borderRadius="1px" />This removes the need for lineIndex and makes the “state machine” easier to follow.
- Use flex instead of magic percentage positioning
You can avoid the 33.33/16.67 calculations by making the connectors flex between circles:
<HStack alignItems="center" justifyContent="space-between">
{steps.map((step, index) => {
const status = getStepStatus(step.number)
return (
<React.Fragment key={step.number}>
<Box alignItems="center">
{/* circle + label */}
</Box>
{index < steps.length - 1 && (
<Box flex={1} mx={2} height="2px" bg="gray.300">
<Box height="100%" {...getLinePropsForStep(step.number + 1)} borderRadius="1px" />
</Box>
)}
</React.Fragment>
)
})}
</HStack>This keeps the same visual intent but removes manual left/right positioning.
- Optional: replace
setIntervalwith CSS-only animation
If acceptable, you can avoid interval management entirely and keep the loading bar animated via CSS:
const getLinePropsForStep = (toStepNumber: number) => {
const status = getStepStatus(toStepNumber)
if (status === 'loading') {
return {
bg: 'blue.500',
width: '100%',
// assuming a keyframe like `@keyframes loadingBar { from { width: 0 } to { width: 100% } }`
animation: 'loadingBar 2s infinite',
}
}
// active/completed/pending same as before, no extra state
}This removes animatedWidth state and useEffect while preserving the animated look.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd41aa55d7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| )} | ||
| </Text> | ||
|
|
||
| <GdOnramperWidget onEvents={handleEvents} apiKey={process.env.REACT_APP_ONRAMPER_KEY} /> |
There was a problem hiding this comment.
Pin the onramp contract environment to production
When this page runs in QA, staging, or the default development-celo environment, omitting env="production" makes the upgraded widget's useBuyGd hook derive its helper from the non-production contract deployment, even though Onramper still purchases real CUSD_CELO on Celo mainnet. The immediately preceding QA integration supplied this override; without it, funds can be directed to the wrong helper/swap deployment and fail to produce the user's production G$, so pass the production contract environment explicitly.
Useful? React with 👍 / 👎.
| )} | ||
| </Text> | ||
|
|
||
| <GdOnramperWidget onEvents={handleEvents} apiKey={process.env.REACT_APP_ONRAMPER_KEY} /> |
There was a problem hiding this comment.
Restore the non-production testing controls
In non-production deployments this call no longer passes isTesting={!isProd}, while GdOnramperWidget defaults isTesting to false and only renders its Next/reset controls when it is true. Consequently the QA and preview environments targeted by this change cannot simulate the received-funds and swap steps without making a real fiat purchase, removing the existing stepper test path.
Useful? React with 👍 / 👎.
Original PR #605
This PR should:
Summary by Sourcery
Integrate the updated Buy G$ flow with the Onramper widget, using the new design and SDK versions while gating purchase functionality behind wallet connection.
New Features:
Enhancements:
Build: