Frontend feat - #602
Conversation
|
@RaymondAbiola Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
📝 WalkthroughWalkthroughAdds a real-time payment-stream progress visualizer and a four-step stream creation wizard with validation, navigation state, responsive UI states, and Vitest/React Testing Library coverage. ChangesStream progress visualization
Stream creation wizard
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant StreamProgressVisualizer
participant useStreamProgress
participant computeStreamProgress
StreamProgressVisualizer->>useStreamProgress: pass stream timing and amounts
useStreamProgress->>computeStreamProgress: compute current progress
useStreamProgress->>computeStreamProgress: recompute on active interval ticks
computeStreamProgress-->>StreamProgressVisualizer: return progress values and status
sequenceDiagram
participant Creator
participant StreamWizard
participant useStreamWizard
participant WizardSteps
Creator->>StreamWizard: enter wizard data
StreamWizard->>WizardSteps: render current step
StreamWizard->>useStreamWizard: validate and advance
useStreamWizard-->>StreamWizard: return errors or next step
Creator->>StreamWizard: submit final review
StreamWizard->>StreamWizard: call onComplete with wizard data
Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx (2)
22-28: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winHardcoded 7-decimal precision ignores per-token granularity.
AMOUNT_PRECISIONis applied to everytokenSymboluniformly. Tokens with fewer decimals (e.g., 6-decimal USDC) will show spurious trailing precision, while others may need more. Consider accepting adecimals/precisionprop derived from the token metadata instead of a fixed constant.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx` around lines 22 - 28, Update formatAmount and its callers in StreamProgressVisualizer to use the token’s metadata-derived decimals/precision rather than the global AMOUNT_PRECISION constant. Pass the appropriate token granularity through the component props or existing token data, preserving locale formatting while matching each token’s configured decimal precision.
9-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider a narrower type for
status.
status: stringallows any value; only"active"is checked (line 81). A union/enum (e.g."active" | "paused" | "completed" | "cancelled") would let the compiler catch typos and unhandled statuses, and would make it easier to correctly branch header/label rendering per state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx` around lines 9 - 20, Update StreamProgressVisualizerProps.status from string to a narrow status union covering the supported stream states, including active, paused, completed, and cancelled. Align the component’s status checks and rendering branches with this typed set while preserving the existing active behavior and ensuring every allowed state has intentional handling.apps/web/src/components/modules/payment-stream/wizard/StreamWizard.test.tsx (1)
25-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest coverage stops at the funding step.
No tests exercise the Schedule step (duration/duration-unit validation, toggles), the Review step, or a full happy-path run that reaches
onComplete. Given this is a new, multi-step user flow, extending coverage through completion would catch regressions in the untested steps (including toggle wiring and duration validation).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/modules/payment-stream/wizard/StreamWizard.test.tsx` around lines 25 - 116, Extend the StreamWizard test suite beyond the funding step to cover Schedule validation for duration and duration-unit fields, schedule toggle behavior, Review rendering, and a complete valid flow that reaches the onComplete callback. Reuse the existing renderWizard and navigation helpers, and verify validation prevents advancement until corrected while the happy path reaches completion with the expected data.apps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsx (1)
61-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDescription textarea error isn't programmatically associated via
aria-describedby.
aria-invalidis set but there's noaria-describedbylinking to theFieldErrortext, so assistive tech users tabbing back into the field won't hear the current error state (only the initial live-region announcement).♿ Suggested association
<textarea id="campaign-description" ... aria-invalid={!!errors.campaignDescription} + aria-describedby={errors.campaignDescription ? "campaign-description-error" : undefined} onChange={(e) => updateField("campaignDescription", e.target.value)} className="..." /> - <FieldError message={errors.campaignDescription} /> + <FieldError id="campaign-description-error" message={errors.campaignDescription} />(
FieldErrorwould need an optionalidprop threaded onto its<p>.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsx` around lines 61 - 72, Update the campaign description textarea in the WizardSteps component to set aria-describedby to a stable error-element ID, and pass that same ID through FieldError so its rendered paragraph receives it. Preserve the existing aria-invalid behavior and ensure the association is present for this field’s validation message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx`:
- Around line 109-151: Update StreamProgressVisualizer.tsx around isStreaming to
derive an isCompleted state from progress.isComplete and render “Stream
completed” before the paused fallback. In StreamProgressVisualizer.test.tsx, add
coverage using status="active" with fake timers and now past endTime, asserting
the completed label appears and the paused label does not.
In `@apps/web/src/components/modules/payment-stream/wizard/StreamWizard.tsx`:
- Around line 37-53: Update handleNext to await onComplete after validating the
final step, handling rejected promises through the component’s existing
user-facing error mechanism instead of fire-and-forget. After onComplete
resolves successfully, call wizard.reset() so the completed wizard is no longer
dirty and useUnsavedChanges stops prompting.
In `@apps/web/src/components/modules/payment-stream/wizard/wizard-config.ts`:
- Around line 104-120: Update validateScheduleStep to require durationValue to
be an integer before calling validateEndTime, rejecting fractional values such
as "0.5" with the existing duration validation error. Preserve the current
required and positive-value checks, and only invoke validateEndTime for valid
positive integers.
In `@apps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsx`:
- Around line 90-98: Move the errors.token rendering from its current location
after the Recipient Address field to immediately alongside the Token AppSelect
in WizardSteps, preserving the existing error content and styling while keeping
other field errors unchanged.
In `@apps/web/src/hooks/use-stream-progress.ts`:
- Around line 110-124: Remove the synchronous setNow(Date.now()) call from the
!isActive branch of the useEffect in use-stream-progress.ts, leaving the early
return intact. Preserve the active-stream interval updates and cleanup behavior.
---
Nitpick comments:
In `@apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx`:
- Around line 22-28: Update formatAmount and its callers in
StreamProgressVisualizer to use the token’s metadata-derived decimals/precision
rather than the global AMOUNT_PRECISION constant. Pass the appropriate token
granularity through the component props or existing token data, preserving
locale formatting while matching each token’s configured decimal precision.
- Around line 9-20: Update StreamProgressVisualizerProps.status from string to a
narrow status union covering the supported stream states, including active,
paused, completed, and cancelled. Align the component’s status checks and
rendering branches with this typed set while preserving the existing active
behavior and ensuring every allowed state has intentional handling.
In `@apps/web/src/components/modules/payment-stream/wizard/StreamWizard.test.tsx`:
- Around line 25-116: Extend the StreamWizard test suite beyond the funding step
to cover Schedule validation for duration and duration-unit fields, schedule
toggle behavior, Review rendering, and a complete valid flow that reaches the
onComplete callback. Reuse the existing renderWizard and navigation helpers, and
verify validation prevents advancement until corrected while the happy path
reaches completion with the expected data.
In `@apps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsx`:
- Around line 61-72: Update the campaign description textarea in the WizardSteps
component to set aria-describedby to a stable error-element ID, and pass that
same ID through FieldError so its rendered paragraph receives it. Preserve the
existing aria-invalid behavior and ensure the association is present for this
field’s validation message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0da4877c-0c88-4ad9-a345-b0afc4aac2f8
📒 Files selected for processing (9)
apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.test.tsxapps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsxapps/web/src/components/modules/payment-stream/wizard/StreamWizard.test.tsxapps/web/src/components/modules/payment-stream/wizard/StreamWizard.tsxapps/web/src/components/modules/payment-stream/wizard/WizardStepper.tsxapps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsxapps/web/src/components/modules/payment-stream/wizard/wizard-config.tsapps/web/src/hooks/use-stream-progress.tsapps/web/src/hooks/use-stream-wizard.ts
| const isStreaming = isActive && progress.hasStarted && !progress.isComplete; | ||
|
|
||
| return ( | ||
| <section | ||
| aria-label="Real-time stream progress" | ||
| className={cn( | ||
| "w-full rounded-lg border border-zinc-700 bg-zinc-800/50 p-4 sm:p-6", | ||
| className | ||
| )} | ||
| > | ||
| <header className="flex flex-wrap items-center justify-between gap-3"> | ||
| <div className="flex items-center gap-2"> | ||
| <span | ||
| className={cn( | ||
| "relative flex h-2.5 w-2.5", | ||
| !isStreaming && "opacity-50" | ||
| )} | ||
| aria-hidden="true" | ||
| > | ||
| {isStreaming && ( | ||
| <motion.span | ||
| className="absolute inline-flex h-full w-full rounded-full bg-green-400" | ||
| animate={{ scale: [1, 2.2, 1], opacity: [0.7, 0, 0.7] }} | ||
| transition={{ duration: 1.6, repeat: Infinity, ease: "easeOut" }} | ||
| /> | ||
| )} | ||
| <span | ||
| className={cn( | ||
| "relative inline-flex h-2.5 w-2.5 rounded-full", | ||
| isStreaming ? "bg-green-500" : "bg-zinc-500" | ||
| )} | ||
| /> | ||
| </span> | ||
| <h3 className="text-sm font-medium text-zinc-300"> | ||
| {isStreaming ? "Streaming live" : "Stream paused"} | ||
| </h3> | ||
| </div> | ||
|
|
||
| <span className="flex items-center gap-1.5 font-mono text-xs text-zinc-400"> | ||
| <Clock className="h-3.5 w-3.5" aria-hidden="true" /> | ||
| {formatCountdown(progress.secondsRemaining)} | ||
| </span> | ||
| </header> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Root cause: no distinct "completed" state rendering, so it's also untested. isStreaming's negation drives the header label for both "paused" and "genuinely completed" cases, so a stream that finished naturally (progress.isComplete === true, status still "active") is shown as "Stream paused." The test suite has no case for this because the behavior doesn't exist yet.
apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx#L109-L151: add anisCompleted = progress.isCompletebranch and render a distinct "Stream completed" label instead of falling through to "Stream paused."apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.test.tsx#L61-L117: once fixed, add a test rendering withstatus="active"and anowpastendTime(via fake timers) asserting the "completed" label appears instead of "paused."
📍 Affects 2 files
apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx#L109-L151(this comment)apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.test.tsx#L61-L117
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/modules/payment-stream/StreamProgressVisualizer.tsx`
around lines 109 - 151, Update StreamProgressVisualizer.tsx around isStreaming
to derive an isCompleted state from progress.isComplete and render “Stream
completed” before the paused fallback. In StreamProgressVisualizer.test.tsx, add
coverage using status="active" with fake timers and now past endTime, asserting
the completed label appears and the paused label does not.
| const wizard = useStreamWizard({ | ||
| defaultToken: tokenOptions[0]?.value ?? "XLM", | ||
| defaultDurationUnit: "day", | ||
| }); | ||
|
|
||
| useUnsavedChanges(wizard.isDirty && !isSubmitting); | ||
|
|
||
| const handleNext = () => { | ||
| if (wizard.isLastStep) { | ||
| if (wizard.validateCurrentStep()) { | ||
| void onComplete(wizard.data); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| wizard.goNext(); | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
handleNext doesn't handle onComplete rejection or reset state after success.
void onComplete(wizard.data) (Line 47) fires-and-forgets an async callback whose signature explicitly allows Promise<void> — a rejection (e.g., a failed on-chain transaction) becomes an unhandled promise rejection with no user-facing feedback. Separately, wizard.reset() is never called after a successful completion, so wizard.isDirty stays true and useUnsavedChanges (Line 42) will continue prompting "leave page?" warnings even though the stream was already created successfully.
🔧 Suggested fix
const handleNext = () => {
if (wizard.isLastStep) {
if (wizard.validateCurrentStep()) {
- void onComplete(wizard.data);
+ Promise.resolve(onComplete(wizard.data))
+ .then(() => wizard.reset())
+ .catch((error) => {
+ console.error("Failed to create stream", error);
+ });
}
return;
}
wizard.goNext();
};📝 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 wizard = useStreamWizard({ | |
| defaultToken: tokenOptions[0]?.value ?? "XLM", | |
| defaultDurationUnit: "day", | |
| }); | |
| useUnsavedChanges(wizard.isDirty && !isSubmitting); | |
| const handleNext = () => { | |
| if (wizard.isLastStep) { | |
| if (wizard.validateCurrentStep()) { | |
| void onComplete(wizard.data); | |
| } | |
| return; | |
| } | |
| wizard.goNext(); | |
| }; | |
| const wizard = useStreamWizard({ | |
| defaultToken: tokenOptions[0]?.value ?? "XLM", | |
| defaultDurationUnit: "day", | |
| }); | |
| useUnsavedChanges(wizard.isDirty && !isSubmitting); | |
| const handleNext = () => { | |
| if (wizard.isLastStep) { | |
| if (wizard.validateCurrentStep()) { | |
| Promise.resolve(onComplete(wizard.data)) | |
| .then(() => wizard.reset()) | |
| .catch((error) => { | |
| console.error("Failed to create stream", error); | |
| }); | |
| } | |
| return; | |
| } | |
| wizard.goNext(); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/modules/payment-stream/wizard/StreamWizard.tsx`
around lines 37 - 53, Update handleNext to await onComplete after validating the
final step, handling rejected promises through the component’s existing
user-facing error mechanism instead of fire-and-forget. After onComplete
resolves successfully, call wizard.reset() so the completed wizard is no longer
dirty and useUnsavedChanges stops prompting.
| function validateScheduleStep(data: WizardStreamData): WizardErrors { | ||
| const errors: WizardErrors = {}; | ||
|
|
||
| const durationValue = Number(data.duration); | ||
| if (!data.duration.trim()) { | ||
| errors.duration = "Duration is required"; | ||
| } else if (!Number.isFinite(durationValue) || durationValue <= 0) { | ||
| errors.duration = "Duration must be greater than 0"; | ||
| } else { | ||
| const endTimeError = validateEndTime(null, data.duration, data.durationUnit); | ||
| if (endTimeError) { | ||
| errors.duration = endTimeError; | ||
| } | ||
| } | ||
|
|
||
| return errors; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Locate target file and relevant symbols"
fd -a 'wizard-config\.ts$|ScheduleStep\.(tsx|ts)$' . | sed 's#^\./##'
echo
echo "Inspect target file around validateScheduleStep and validateEndTime references"
target="$(fd 'wizard-config\.ts$' . | head -n 1)"
if [ -n "$target" ]; then
wc -l "$target"
sed -n '1,180p' "$target" | nl -ba
fi
echo
echo "Search for validateEndTime implementation/usages"
rg -n "function validateEndTime|const validateEndTime|validateEndTime|Duration must be greater" .Repository: Fundable-Protocol/stellar_client_os
Length of output: 501
🏁 Script executed:
#!/bin/bash
set -euo pipefail
target="/home/jailuser/git/apps/web/src/components/modules/payment-stream/wizard/wizard-config.ts"
echo
echo "Target file lines with line numbers"
awk '{printf "%6d %s\n", NR, $0}' "$target"
echo
echo "ScheduleStep candidate files and relevant parsing/end-time code"
fd 'ScheduleStep\.(tsx|ts)$' . | while read -r f; do
echo "--- $f"
wc -l "$f"
rg -n "calculateEndTime|duration|startTime|useMemo|type=\"number\"|step=\"|parse|Number\\(" "$f" -C 3
done
echo
echo "Runtime probe for current input semantics"
node - <<'JS'
const inputs = ["0", "0.5", "1.5", "abc", " ", "1", "-1"];
for (const v of inputs) {
const n = Number(v);
console.log(JSON.stringify({ input: v, isFinite: Number.isFinite(n), pos: n > 0, parseInt: parseInt(v), NumberInteger: Number.isInteger(n) }));
}
JSRepository: Fundable-Protocol/stellar_client_os
Length of output: 6215
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find stream-validation file"
fd 'stream-validation\.(ts|tsx)$|stream-validation$' .
validation_file="$(fd 'stream-validation\.ts$' . | head -n 1 || true)"
if [ -n "$validation_file" ]; then
echo "--- $validation_file"
awk '{printf "%6d %s\n", NR, $0}' "$validation_file"
fi
schedule_step="$(fd 'ScheduleStep\.(tsx|ts)$' . | head -n 1 || true)"
if [ -n "$schedule_step" ]; then
echo "--- schedule step parsing/end-time references"
rg -n "Duration|duration|start|endTime|calculate|validateWizardStep|type=\"number\"|step=\"|Number\\(|parseInt" "$schedule_step" -C 3
fi
# Probe all likely candidate files containing stream duration end-time parsing
rg -n "validateEndTime|calculateEndTime|Duration must be greater|Number\\(.*duration|durationUnit|startTime|endTime" . \
-g '*.ts' -g '*.tsx' -g '!**/node_modules/**' -g '!**/.next/**' -C 2Repository: Fundable-Protocol/stellar_client_os
Length of output: 50393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Find ScheduleStep candidates"
fd 'ScheduleStep\.tsx$|ScheduleStep\.ts$' .
schedule="$(fd 'ScheduleStep\.tsx$|ScheduleStep\.ts$' . | head -n 1 || true)"
if [ -n "$schedule" ]; then
echo "--- $schedule"
wc -l "$schedule"
rg -n "duration|calculateEndTime|validateWizardStep|Input|type=\"number\"|step=\"|parse|int|Number\\(|preview|endTime|formatEndTime|getRelativeTime|useMemo" "$schedule" -C 3
echo "--- lines 80-150"
awk '{printf "%6d %s\n", NR, $0}' "$schedule" | sed -n '80,150p'
fi
echo
echo "Find wizard ScheduleStep references and local calculations"
rg -n "calculateEndTime|calculateStreamEndTime|formatEndTime|getRelativeTime|durationValue|durationUnit|wizard-config|validateWizardStep.*schedule|stepId === \"schedule\"" apps/web/src/components/modules/payment-stream -g '*.tsx' -g '*.ts' | head -n 80Repository: Fundable-Protocol/stellar_client_os
Length of output: 7196
🏁 Script executed:
#!/bin/bash
set -euo pipefail
wizard_steps="/home/jailuser/git/apps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsx"
form="/home/jailuser/git/apps/web/src/components/modules/payment-stream/PaymentStreamForm.tsx"
stepper="/home/jailuser/git/apps/web/src/components/modules/payment-stream/wizard/WizardStepper.tsx"
for f in "$wizard_steps" "$form" "$stepper"; do
if [ -f "$f" ]; then
echo "--- $f"
wc -l "$f"
fi
done
if [ -f "$wizard_steps" ]; then
echo "--- WizardSteps relevant sections"
awk '{printf "%6d %s\n", NR, $0}' "$wizard_steps" | sed -n '1,280p'
fi
if [ -f "$form" ]; then
echo "--- PaymentStreamForm relevant sections"
awk '{printf "%6d %s\n", NR, $0}' "$form" | sed -n '1,260p'
fiRepository: Fundable-Protocol/stellar_client_os
Length of output: 22116
Reject non-integers in the wizard before calling validateEndTime.
validateScheduleStep() accepts values like "0.5" via Number.parse, but validateEndTime() parses via parseInt(...), so "0.5" is converted to 0 and fails with Duration must be greater than zero. The schedule preview also computes with Number(...), so fractional inputs can show an end-time preview while validation blocks progress. Add integer parsing at the source of truth and reject non-integers there.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/modules/payment-stream/wizard/wizard-config.ts`
around lines 104 - 120, Update validateScheduleStep to require durationValue to
be an integer before calling validateEndTime, rejecting fractional values such
as "0.5" with the existing duration validation error. Preserve the current
required and positive-value checks, and only invoke validateEndTime for valid
positive integers.
| <AppSelect | ||
| className="h-12" | ||
| titleClassName="text-zinc-300" | ||
| title="Token" | ||
| options={tokenOptions} | ||
| value={data.token} | ||
| setValue={(value) => updateField("token", value)} | ||
| placeholder={data.token || "Select a token"} | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Token error rendered far from the Token select it describes.
errors.token (Line 121) is displayed after the Recipient Address field, not near the AppSelect for Token (Lines 90-98), which can mislead users about which field the error applies to.
💡 Move the token error next to its field
<AppSelect
className="h-12"
titleClassName="text-zinc-300"
title="Token"
options={tokenOptions}
value={data.token}
setValue={(value) => updateField("token", value)}
placeholder={data.token || "Select a token"}
/>
+ <FieldError message={errors.token} />
<InputWithLabel
title="Total Amount"
...
/>
</div>
<InputWithLabel
title="Recipient Address"
...
/>
-
- <FieldError message={errors.token} />
</div>Also applies to: 121-121
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/components/modules/payment-stream/wizard/WizardSteps.tsx` around
lines 90 - 98, Move the errors.token rendering from its current location after
the Recipient Address field to immediately alongside the Token AppSelect in
WizardSteps, preserving the existing error content and styling while keeping
other field errors unchanged.
| useEffect(() => { | ||
| if (!isActive) { | ||
| setNow(Date.now()); | ||
| return; | ||
| } | ||
|
|
||
| const interval = setInterval(() => { | ||
| const current = Date.now(); | ||
| setNow(current); | ||
| // Nothing left to accrue once the stream ends — stop burning frames. | ||
| if (current >= endTime) clearInterval(interval); | ||
| }, tickMs); | ||
|
|
||
| return () => clearInterval(interval); | ||
| }, [isActive, tickMs, endTime]); |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
Drop the synchronous setNow call inside the effect body.
Static analysis flags this correctly: calling setNow(Date.now()) directly in the effect body (line 112) causes an avoidable extra render on mount/every isActive/tickMs/endTime change for paused streams. Since the interval already keeps now within tickMs (100ms) of real time, this branch's sync-then-return isn't needed to "freeze" state accurately — removing it only introduces at most ~100ms of staleness, which is imperceptible, while eliminating the cascading-render risk.
⚡ Proposed fix
useEffect(() => {
- if (!isActive) {
- setNow(Date.now());
- return;
- }
+ if (!isActive) return;
const interval = setInterval(() => {
const current = Date.now();
setNow(current);
// Nothing left to accrue once the stream ends — stop burning frames.
if (current >= endTime) clearInterval(interval);
}, tickMs);
return () => clearInterval(interval);
}, [isActive, tickMs, endTime]);📝 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.
| useEffect(() => { | |
| if (!isActive) { | |
| setNow(Date.now()); | |
| return; | |
| } | |
| const interval = setInterval(() => { | |
| const current = Date.now(); | |
| setNow(current); | |
| // Nothing left to accrue once the stream ends — stop burning frames. | |
| if (current >= endTime) clearInterval(interval); | |
| }, tickMs); | |
| return () => clearInterval(interval); | |
| }, [isActive, tickMs, endTime]); | |
| useEffect(() => { | |
| if (!isActive) return; | |
| const interval = setInterval(() => { | |
| const current = Date.now(); | |
| setNow(current); | |
| // Nothing left to accrue once the stream ends — stop burning frames. | |
| if (current >= endTime) clearInterval(interval); | |
| }, tickMs); | |
| return () => clearInterval(interval); | |
| }, [isActive, tickMs, endTime]); |
🧰 Tools
🪛 ESLint
[error] 112-112: Error: Calling setState synchronously within an effect can trigger cascading renders
Effects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:
- Update external systems with the latest state from React.
- Subscribe for updates from some external system, calling setState in a callback function when external state changes.
Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).
/home/jailuser/git/apps/web/src/hooks/use-stream-progress.ts:112:7
110 | useEffect(() => {
111 | if (!isActive) {
112 | setNow(Date.now());
| ^^^^^^ Avoid calling setState() directly within an effect
113 | return;
114 | }
115 |
(react-hooks/set-state-in-effect)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/hooks/use-stream-progress.ts` around lines 110 - 124, Remove the
synchronous setNow(Date.now()) call from the !isActive branch of the useEffect
in use-stream-progress.ts, leaving the early return intact. Preserve the
active-stream interval updates and cleanup behavior.
Source: Linters/SAST tools
|
dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates |
Closes #523
Closes #526
Summary by CodeRabbit
New Features
Tests