feat(samples/kotlin): add exchange-rates lookup flow#602
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
2f6e65c to
f81b79b
Compare
Greptile SummaryThis PR adds an Exchange Rates lookup flow: a Kotlin backend route (
Confidence Score: 4/5Safe to merge with one small fix: the sending-amount input should restrict to integers to match the backend's long parser. The Kotlin route and React wiring are clean and follow existing patterns. The one active defect is in GetExchangeRate.tsx: the number input lacks step="1", so a user who enters a decimal amount (e.g. 100.50) will have it silently discarded by the backend's toLongOrNull() call — the API call proceeds but the amount the user typed is not applied, with no error shown. samples/frontend/src/steps/GetExchangeRate.tsx — the sending-amount number input.
|
| Filename | Overview |
|---|---|
| samples/frontend/src/steps/GetExchangeRate.tsx | New form step with currency selects and a sending amount field. Missing step="1" on the number input means decimal values are silently dropped by the backend's toLongOrNull() without any user feedback. |
| samples/kotlin/src/main/kotlin/com/grid/sample/routes/ExchangeRates.kt | New GET /api/exchange-rates route that proxies to client.exchangeRates().list(). Follows existing route patterns; the unescaped e.message interpolation in the error response (line 45) was flagged in a prior review thread. |
| samples/frontend/src/flows/ExchangeRatesFlow.tsx | Thin flow wrapper rendering GetExchangeRate inside a styled card. No issues. |
| samples/frontend/src/App.tsx | Wires ExchangeRatesFlow into the app and changes the default active flow to 'exchange-rates'. Default-flow change was flagged in a prior review thread. |
| samples/frontend/src/components/Sidebar.tsx | Adds 'exchange-rates' to the FlowKey union and inserts the sidebar entry. Clean change, no issues. |
| samples/kotlin/src/main/kotlin/com/grid/sample/Application.kt | Registers the new exchangeRateRoutes() in the correct position. No issues. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User
participant GetExchangeRate as GetExchangeRate.tsx
participant Backend as Kotlin /api/exchange-rates
participant GridAPI as Grid API (exchangeRates().list())
User->>GetExchangeRate: "Select currencies & enter amount, click "Get Exchange Rate""
GetExchangeRate->>Backend: "GET /api/exchange-rates?sourceCurrency=&destinationCurrency=&sendingAmount="
Backend->>Backend: Validate sourceCurrency (400 if missing)
Backend->>GridAPI: ExchangeRateListParams.builder().build()
GridAPI-->>Backend: Exchange rate list response
Backend->>Backend: JsonUtils.prettyPrint(response)
Backend-->>GetExchangeRate: 200 OK (JSON)
GetExchangeRate-->>User: Display in ResponsePanel
note over Backend,GetExchangeRate: On exception: {"error": "${e.message}"} — 500
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User
participant GetExchangeRate as GetExchangeRate.tsx
participant Backend as Kotlin /api/exchange-rates
participant GridAPI as Grid API (exchangeRates().list())
User->>GetExchangeRate: "Select currencies & enter amount, click "Get Exchange Rate""
GetExchangeRate->>Backend: "GET /api/exchange-rates?sourceCurrency=&destinationCurrency=&sendingAmount="
Backend->>Backend: Validate sourceCurrency (400 if missing)
Backend->>GridAPI: ExchangeRateListParams.builder().build()
GridAPI-->>Backend: Exchange rate list response
Backend->>Backend: JsonUtils.prettyPrint(response)
Backend-->>GetExchangeRate: 200 OK (JSON)
GetExchangeRate-->>User: Display in ResponsePanel
note over Backend,GetExchangeRate: On exception: {"error": "${e.message}"} — 500
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
samples/frontend/src/steps/GetExchangeRate.tsx:57-59
Decimal `sendingAmount` is silently dropped by the backend. The Kotlin route parses the query parameter with `toLongOrNull()`, which returns `null` for any non-integer value (e.g. `"100.50"` → `null`). Because the `<input type="number">` has no `step` attribute, browsers treat any numeric value as valid, so a user who types `100.50` will see the form submit but their amount is quietly ignored — the API call proceeds without a `sendingAmount` and returns a result the user may misread as corresponding to their input.
```suggestion
type="number"
min="0"
step="1"
value={sendingAmount}
```
Reviews (2): Last reviewed commit: "feat(samples/kotlin): add exchange-rates..." | Re-trigger Greptile
| call.respondText( | ||
| """{"error": "${e.message}"}""", | ||
| ContentType.Application.Json, | ||
| HttpStatusCode.InternalServerError | ||
| ) |
There was a problem hiding this comment.
Unescaped
e.message produces malformed JSON
If the exception message contains a " character (e.g. Invalid currency "XYZ") or a newline, the interpolated string is not valid JSON. The frontend's JSON.parse call will throw, masking the real error with a confusing parse failure instead. Using JsonUtils.mapper.writeValueAsString(e.message) would produce a properly escaped JSON string value.
| call.respondText( | |
| """{"error": "${e.message}"}""", | |
| ContentType.Application.Json, | |
| HttpStatusCode.InternalServerError | |
| ) | |
| call.respondText( | |
| """{"error": ${JsonUtils.mapper.writeValueAsString(e.message)}}""", | |
| ContentType.Application.Json, | |
| HttpStatusCode.InternalServerError | |
| ) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: samples/kotlin/src/main/kotlin/com/grid/sample/routes/ExchangeRates.kt
Line: 44-48
Comment:
**Unescaped `e.message` produces malformed JSON**
If the exception message contains a `"` character (e.g. `Invalid currency "XYZ"`) or a newline, the interpolated string is not valid JSON. The frontend's `JSON.parse` call will throw, masking the real error with a confusing parse failure instead. Using `JsonUtils.mapper.writeValueAsString(e.message)` would produce a properly escaped JSON string value.
```suggestion
call.respondText(
"""{"error": ${JsonUtils.mapper.writeValueAsString(e.message)}}""",
ContentType.Application.Json,
HttpStatusCode.InternalServerError
)
```
How can I resolve this? If you propose a fix, please make it concise.| <div className="grid grid-cols-2 gap-3 mb-3"> | ||
| <div> | ||
| <label htmlFor="src-currency" className="block text-sm font-medium text-gray-300 mb-1">Source Currency</label> | ||
| <select id="src-currency" value={sourceCurrency} onChange={(e) => setSourceCurrency(e.target.value)} disabled={loading} className={selectClass}> | ||
| {CURRENCIES.map((c) => <option key={c} value={c}>{c}</option>)} | ||
| </select> | ||
| </div> | ||
| <div> | ||
| <label htmlFor="dst-currency" className="block text-sm font-medium text-gray-300 mb-1">Destination Currency</label> | ||
| <select id="dst-currency" value={destinationCurrency} onChange={(e) => setDestinationCurrency(e.target.value)} disabled={loading} className={selectClass}> | ||
| {CURRENCIES.map((c) => <option key={c} value={c}>{c}</option>)} | ||
| </select> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
No guard against selecting identical source and destination currencies
The two selects share the same CURRENCIES list with no cross-validation, so a user can choose USD → USD. Depending on the API behaviour, this either returns a trivial result or surfaces an API error only in the response panel.
Prompt To Fix With AI
This is a comment left during a code review.
Path: samples/frontend/src/steps/GetExchangeRate.tsx
Line: 39-52
Comment:
**No guard against selecting identical source and destination currencies**
The two selects share the same `CURRENCIES` list with no cross-validation, so a user can choose `USD → USD`. Depending on the API behaviour, this either returns a trivial result or surfaces an API error only in the response panel.
How can I resolve this? If you propose a fix, please make it concise.Add an "Exchange Rates" flow that looks up the cached FX rate, fees, and receiving amount for a corridor. Backend: GET /api/exchange-rates proxies to client.exchangeRates().list(). Frontend: a GetExchangeRate form (source/destination currency + sending amount) and ExchangeRatesFlow, wired into the sidebar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f81b79b to
8e425ad
Compare
| type="number" | ||
| min="0" | ||
| value={sendingAmount} |
There was a problem hiding this comment.
Decimal
sendingAmount is silently dropped by the backend. The Kotlin route parses the query parameter with toLongOrNull(), which returns null for any non-integer value (e.g. "100.50" → null). Because the <input type="number"> has no step attribute, browsers treat any numeric value as valid, so a user who types 100.50 will see the form submit but their amount is quietly ignored — the API call proceeds without a sendingAmount and returns a result the user may misread as corresponding to their input.
| type="number" | |
| min="0" | |
| value={sendingAmount} | |
| type="number" | |
| min="0" | |
| step="1" | |
| value={sendingAmount} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: samples/frontend/src/steps/GetExchangeRate.tsx
Line: 57-59
Comment:
Decimal `sendingAmount` is silently dropped by the backend. The Kotlin route parses the query parameter with `toLongOrNull()`, which returns `null` for any non-integer value (e.g. `"100.50"` → `null`). Because the `<input type="number">` has no `step` attribute, browsers treat any numeric value as valid, so a user who types `100.50` will see the form submit but their amount is quietly ignored — the API call proceeds without a `sendingAmount` and returns a result the user may misread as corresponding to their input.
```suggestion
type="number"
min="0"
step="1"
value={sendingAmount}
```
How can I resolve this? If you propose a fix, please make it concise.
Add an "Exchange Rates" flow that looks up the cached FX rate, fees, and
receiving amount for a corridor. Backend: GET /api/exchange-rates proxies
to client.exchangeRates().list(). Frontend: a GetExchangeRate form
(source/destination currency + sending amount) and ExchangeRatesFlow,
wired into the sidebar.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com