Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR consolidates modal management logic by removing duplicate balance warning effects and introducing a centralized modal coordination system. It adds a new leaderboard feature with time filtering, refactors perk reward handling across transaction detail components with new types and utilities, and enhances QR pay interactions. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Multiple heterogeneous features with significant logic density: new modal coordination system with observable registry pattern, substantial refactoring of perk reward handling across multiple transaction components with type changes, new leaderboard page with stateful filtering and API integration, and QR pay interactions. Requires understanding interactions between new modal system, perk data flow transformations, and transaction detail rendering paths. Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (9)
src/app/(mobile-ui)/qr-pay/page.tsx (1)
936-945: Consider extracting perk text logic for testability.The inline function determining perk banner text works correctly but could be extracted to a helper function for improved testability and reusability. The three-case logic (100%, >100%, else) is clear but would benefit from unit testing.
Example extraction:
function getPerkBannerText(discountPercentage: number): string { if (discountPercentage === 100) { return 'We paid for this bill! Earn points, climb tiers and unlock even better perks.' } else if (discountPercentage > 100) { return `We gave you ${discountPercentage}% back — that's more than you paid! Earn points, climb tiers and unlock even better perks.` } else { return `We gave you ${discountPercentage}% cashback! Earn points, climb tiers and unlock even better perks.` } }src/services/points.ts (1)
106-108: Limit param check should allow 0 if ever needed.Use explicit undefined check instead of truthiness to avoid silently dropping 0.
- if (params?.limit) queryParams.append('limit', params.limit.toString()) + if (params?.limit !== undefined) queryParams.append('limit', params.limit.toString())src/app/(mobile-ui)/dev/leaderboard/page.tsx (3)
72-73: Fix timer typings for browser code.NodeJS.Timeout can mis-type in DOM builds. Prefer ReturnType.
- const debounceTimerRef = useRef<NodeJS.Timeout | null>(null) + const debounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
107-115: Avoid polluting history during typing — use replace for debounced updates.Typing in the datetime input pushes a new entry per keystroke. Use router.replace for debounced updates; keep push for explicit actions.
- const updateURL = useCallback( - (timestamp: string) => { + const updateURL = useCallback( + (timestamp: string, { replace = false }: { replace?: boolean } = {}) => { const params = new URLSearchParams() params.set('since', timestamp) - router.push(`/dev/leaderboard?${params.toString()}`, { scroll: false }) + const url = `/dev/leaderboard?${params.toString()}` + replace ? router.replace(url, { scroll: false }) : router.push(url, { scroll: false }) }, [router] ) @@ - updateURL(timestamp) - debouncedFetch(timestamp) + updateURL(timestamp, { replace: true }) + debouncedFetch(timestamp)Also applies to: 190-208
318-324: “Last update” should reflect data, not initial mount.Initialize as null and render only after first successful fetch to avoid a misleading timestamp.
- const [lastUpdate, setLastUpdate] = useState<Date>(new Date()) + const [lastUpdate, setLastUpdate] = useState<Date | null>(null)- <span className="text-sm text-gray-500">Last update: {lastUpdate.toLocaleTimeString()}</span> + <span className="text-sm text-gray-500"> + Last update: {lastUpdate ? lastUpdate.toLocaleTimeString() : '—'} + </span>Also applies to: 86-90
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (1)
181-183: Dead code toggle: peanutFee is hard-disabled.rowVisibilityConfig.peanutFee is false while a UI row still exists later. Remove the unreachable row or leave a clear TODO to avoid confusion.
- peanutFee: false, // Perk fee logic removed - perks now show as separate transactions + // Perk fee logic removed - perks now show as separate transactions + // peanutFee remains off; consider deleting the row below. + peanutFee: false,Optionally delete lines 963–969 rendering the Peanut fee row.
Also applies to: 963-969
src/components/TransactionDetails/transactionTransformer.ts (3)
70-79: Avoid inline 'perk' shape; extract a named domain type and clarify semanticsDefine a shared type (e.g., HistoryEntryPerkOffer) in services.types and use it here to prevent drift and clarify that this is the offer/claim context, distinct from perkReward (the event). Also document units for discountPercentage (0–100 vs 0–1).
Apply in this file:
-import { type HistoryEntryPerkReward } from '@/services/services.types' +import { type HistoryEntryPerkReward, type HistoryEntryPerkOffer } from '@/services/services.types'- perk?: { - claimed: boolean - discountPercentage: number - amountSponsored?: number - txHash?: string - merchantInfo?: { - promoDescription?: string - } - } + perk?: HistoryEntryPerkOfferAdd in services.types (example):
export interface HistoryEntryPerkOffer { claimed: boolean discountPercentage: number // percent 0–100 amountSponsored?: number txHash?: string merchantInfo?: { promoDescription?: string } }Ensure no other files define a conflicting shape for perk/offer.
512-521: Avoid unsafeascasts for API data; add a narrow normalizer/guardCasting unknown API shapes can mask bugs. Add small validators to coerce/guard fields.
- perkReward: entry.extraData?.perkReward as HistoryEntryPerkReward | undefined, - perk: entry.extraData?.perk as - | { - claimed: boolean - discountPercentage: number - amountSponsored?: number - txHash?: string - merchantInfo?: { promoDescription?: string } - } - | undefined, + perkReward: normalizePerkReward(entry.extraData?.perkReward), + perk: normalizePerkOffer(entry.extraData?.perk),Add helpers (example, place below function or in a utils module):
function normalizePerkReward(raw: any): HistoryEntryPerkReward | undefined { if (!raw) return undefined const discount = Number(raw.discountPercentage) return { reason: String(raw.reason ?? ''), discountPercentage: Number.isFinite(discount) ? discount : 0, originatingTxId: raw.originatingTxId ? String(raw.originatingTxId) : undefined, originatingTxType: raw.originatingTxType ? String(raw.originatingTxType) : undefined, perkName: raw.perkName ? String(raw.perkName) : undefined, } } function normalizePerkOffer(raw: any): HistoryEntryPerkOffer | undefined { if (!raw) return undefined const discount = Number(raw.discountPercentage) return { claimed: Boolean(raw.claimed), discountPercentage: Number.isFinite(discount) ? discount : 0, amountSponsored: raw.amountSponsored != null ? Number(raw.amountSponsored) : undefined, txHash: raw.txHash ? String(raw.txHash) : undefined, merchantInfo: raw.merchantInfo?.promoDescription ? { promoDescription: String(raw.merchantInfo.promoDescription) } : undefined, } }Confirm these helpers align with the server payload shapes to avoid accidental data loss.
333-339: Use data-driven display names instead of hard-coded copy for i18n supportThe suggested refactor is valid. The
perkNameandreasonproperties exist on theHistoryEntryPerkRewardtype. This change aligns with the existing pattern in the same switch statement where other transaction types derive values from theentryobject (e.g., lines 325–330 for merchant payments, line 315 for deposits).case EHistoryEntryType.PERK_REWARD: direction = 'receive' transactionCardType = 'receive' nameForDetails = - 'Peanut Perk!' + entry.extraData?.perkReward?.perkName ?? + entry.extraData?.perkReward?.reason ?? + 'Peanut Perk!' fullName = 'Peanut Perks' isPeerActuallyUser = false breakNote: Other occurrences of "Peanut Perk" in TransactionDetailsReceipt.tsx and qr-pay/page.tsx appear to be static UI labels; consider centralizing these separately if i18n coverage is needed beyond the data transformation layer.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
MINIMAL_MODAL_FIX.md(1 hunks)MODAL_MANAGER_PR_PLAN.md(1 hunks)src/app/(mobile-ui)/dev/leaderboard/page.tsx(1 hunks)src/app/(mobile-ui)/dev/page.tsx(1 hunks)src/app/(mobile-ui)/home/page.tsx(1 hunks)src/app/(mobile-ui)/qr-pay/page.tsx(2 hunks)src/components/TransactionDetails/PerkIcon.tsx(1 hunks)src/components/TransactionDetails/TransactionCard.tsx(4 hunks)src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx(1 hunks)src/components/TransactionDetails/TransactionDetailsReceipt.tsx(5 hunks)src/components/TransactionDetails/transactionTransformer.ts(4 hunks)src/services/points.ts(1 hunks)src/services/services.types.ts(1 hunks)src/utils/history.utils.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/app/(mobile-ui)/dev/leaderboard/page.tsx (3)
src/services/points.ts (1)
pointsApi(6-131)src/components/Global/Icons/Icon.tsx (1)
Icon(206-215)src/components/0_Bruddle/Button.tsx (1)
Button(76-267)
src/services/points.ts (2)
src/utils/sentry.utils.ts (1)
fetchWithSentry(36-150)src/constants/general.consts.ts (1)
PEANUT_API_URL(43-47)
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (5)
src/hooks/useTransactionHistory.ts (1)
EHistoryEntryType(11-11)src/components/TransactionDetails/PerkIcon.tsx (1)
PerkIcon(30-40)src/components/Payment/PaymentInfoRow.tsx (1)
PaymentInfoRow(18-87)src/utils/general.utils.ts (1)
formatDate(932-944)src/components/Global/Icons/Icon.tsx (1)
Icon(206-215)
src/components/TransactionDetails/transactionTransformer.ts (1)
src/services/services.types.ts (1)
HistoryEntryPerkReward(466-472)
src/components/TransactionDetails/TransactionCard.tsx (2)
src/hooks/useTransactionHistory.ts (1)
EHistoryEntryType(11-11)src/components/TransactionDetails/PerkIcon.tsx (1)
PerkIcon(30-40)
🪛 markdownlint-cli2 (0.18.1)
MINIMAL_MODAL_FIX.md
72-72: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
103-103: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🔇 Additional comments (16)
src/app/(mobile-ui)/dev/page.tsx (1)
10-16: LGTM! Clean addition of the leaderboard dev tool.The new entry follows the established pattern and integrates well with the existing dev tools list.
MODAL_MANAGER_PR_PLAN.md (2)
1-50: Documentation: Modal Manager Implementation PlanThis appears to be a planning document for future work, marked as "Planned for post-release" (line 3). The proposed priority-based modal coordination system is well-designed, but since this is deferred work, ensure it's clearly tracked and not blocking the current PR.
283-439: Example implementation code in planning document.The proposed TypeScript implementation looks reasonable, with proper use of hooks, global state management, and priority-based coordination. Since this is deferred to a future PR, no action needed now.
src/utils/history.utils.ts (1)
25-25: LGTM! Clean addition of PERK_REWARD entry type.The new enum member integrates correctly with the existing historyTypeToNumber/historyTypeFromNumber utility functions.
MINIMAL_MODAL_FIX.md (1)
1-101: Documentation: Duplicate Modal FixThis document clearly describes the duplicate balance warning modal bug and the fix applied in home/page.tsx. The documentation matches the actual implementation changes.
Note: Static analysis flagged minor markdown formatting issues (lines 72, 103) suggesting headings instead of emphasis, but these don't affect functionality.
src/components/TransactionDetails/PerkIcon.tsx (1)
1-40: LGTM! Well-structured reusable component.The PerkIcon component is clean and follows React best practices:
- Proper TypeScript typing
- Configurable sizing
- Good use of Next.js Image optimization
- Appropriate alt text for accessibility
src/app/(mobile-ui)/home/page.tsx (1)
168-175: LGTM! Correctly fixes the duplicate modal effect issue.This change consolidates the duplicate balance warning modal effects by adding the missing
isPostSignupActionModalVisibledependency. The dependency array now correctly includes all values referenced in the effect body (line 163), preventing stale closures and ensuring proper modal coordination.This fix aligns with the documented solution in MINIMAL_MODAL_FIX.md.
src/app/(mobile-ui)/qr-pay/page.tsx (1)
970-981: LGTM! Excellent mobile interaction handling.The added event handlers and styles properly prevent unintended mobile interactions:
onContextMenuprevents long-press context menutouch-manipulationoptimizes touch event handlingselect-noneprevents text selection during hold gesture- iOS-specific webkit styles prevent callout and tap highlighting
These improvements enhance the hold-to-claim UX on mobile devices.
src/components/TransactionDetails/TransactionCard.tsx (3)
22-23: LGTM! Proper imports for perk reward support.The new imports correctly support the PERK_REWARD transaction type rendering.
76-76: LGTM! Clean conditional rendering for perk rewards.The isPerkReward check (line 76) correctly identifies PERK_REWARD transactions, and the avatar rendering logic (lines 109-114) cleanly handles three cases:
- Perk rewards → PerkIcon
- Transactions with avatarUrl → Image
- Other transactions → TransactionAvatarBadge
StatusPill is consistently rendered across all cases when present.
Also applies to: 109-114
154-154: LGTM! Appropriate action text for perk rewards.Displaying "Refund" for perk reward transactions is semantically correct and user-friendly.
src/components/TransactionDetails/TransactionDetailsHeaderCard.tsx (1)
225-228: Amount strike-through tied only to cancel — good simplification.Removing perk-driven strike-through avoids mixed semantics in amount styling. Looks correct.
src/services/services.types.ts (1)
465-472: Verify actual backend API constraints before narrowing types.The originatingTxType field appears unused throughout the codebase (no assignments or reads found), while the reason field is only accessed for display without validation. The suggestions to narrow originatingTxType to HistoryEntryType and brand the reason field depend on backend API constraints that aren't enforced or validated in the frontend code. Before implementing these refactors, confirm with your backend team whether:
- originatingTxType is actively populated with HistoryEntryType values
- reason has a defined set of known values that should be enforced
- Both fields are essential or if originatingTxId (currently commented out) indicates incomplete implementation
src/components/TransactionDetails/TransactionDetailsReceipt.tsx (2)
351-353: The enum value EHistoryEntryType.PERK_REWARD exists and the code will compile successfully.The verification confirms that
EHistoryEntryType.PERK_REWARDis properly defined in the enum and correctly exported through the import chain. The filesrc/hooks/useTransactionHistory.ts(line 11) re-exportsEHistoryEntryTypefrom@/utils/history.utils, where the PERK_REWARD member is defined (line 25 in the enum). The import path in TransactionDetailsReceipt.tsx is correct.
391-397: Incorrect — transaction.date is provided by the transformer; no crash expected.TransactionDetails.date is non-optional and is set in mapTransactionDataForDrawer (src/components/TransactionDetails/transactionTransformer.ts →
date: new Date(entry.timestamp)); TransactionDetailsReceipt also returns early when!transaction, soformatDate(new Date(transaction.date))is safe. If you’re worried about malformed upstream timestamps, validateentry.timestampin the transformer or makeformatDatetolerate invalid Dates.Likely an incorrect or invalid review comment.
src/components/TransactionDetails/transactionTransformer.ts (1)
16-16: Type-only import for perk reward — LGTMGood use of a type import to avoid runtime cost. No issues spotted.
| {/* Perk eligibility banner */} | ||
| {transaction.extraDataForDrawer?.perk?.claimed && transaction.status !== 'pending' && ( | ||
| <Card position="single" className="px-4 py-4"> | ||
| <div className="flex items-center gap-3"> | ||
| <PerkIcon size="small" /> | ||
| <div className="flex flex-col gap-1"> | ||
| <span className="font-semibold text-gray-900">Eligible for a Peanut Perk!</span> | ||
| <span className="text-sm text-gray-600"> | ||
| {(() => { | ||
| const percentage = transaction.extraDataForDrawer.perk.discountPercentage | ||
| const amount = transaction.extraDataForDrawer.perk.amountSponsored | ||
| const amountStr = amount ? `$${amount.toFixed(2)}` : '' | ||
|
|
||
| if (percentage === 100) { | ||
| return `You received a full refund${amount ? ` (${amountStr})` : ''} as a Peanut Perk.` | ||
| } else if (percentage > 100) { | ||
| return `You received ${percentage}% back${amount ? ` (${amountStr})` : ''} — that's more than you paid!` | ||
| } else { | ||
| return `You received ${percentage}% cashback${amount ? ` (${amountStr})` : ''} as a Peanut Perk.` | ||
| } | ||
| })()} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </Card> | ||
| )} |
There was a problem hiding this comment.
Inconsistent perk shape vs new types.
This block reads extraDataForDrawer.perk.claimed and amountSponsored, but the new type HistoryEntryPerkReward removes amountSponsored and renames the entity. Align on perkReward and avoid amountSponsored-dependent copy.
- {transaction.extraDataForDrawer?.perk?.claimed && transaction.status !== 'pending' && (
+ {transaction.extraDataForDrawer?.perkReward && transaction.status !== 'pending' && (
<Card position="single" className="px-4 py-4">
<div className="flex items-center gap-3">
<PerkIcon size="small" />
<div className="flex flex-col gap-1">
<span className="font-semibold text-gray-900">Eligible for a Peanut Perk!</span>
<span className="text-sm text-gray-600">
- {(() => {
- const percentage = transaction.extraDataForDrawer.perk.discountPercentage
- const amount = transaction.extraDataForDrawer.perk.amountSponsored
- const amountStr = amount ? `$${amount.toFixed(2)}` : ''
- if (percentage === 100) return `You received a full refund${amount ? ` (${amountStr})` : ''} as a Peanut Perk.`
- else if (percentage > 100) return `You received ${percentage}% back${amount ? ` (${amountStr})` : ''} — that's more than you paid!`
- else return `You received ${percentage}% cashback${amount ? ` (${amountStr})` : ''} as a Peanut Perk.`
- })()}
+ {`You received ${transaction.extraDataForDrawer.perkReward.discountPercentage}% as a Peanut Perk.`}
</span>
</div>
</div>
</Card>
)}📝 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.
| {/* Perk eligibility banner */} | |
| {transaction.extraDataForDrawer?.perk?.claimed && transaction.status !== 'pending' && ( | |
| <Card position="single" className="px-4 py-4"> | |
| <div className="flex items-center gap-3"> | |
| <PerkIcon size="small" /> | |
| <div className="flex flex-col gap-1"> | |
| <span className="font-semibold text-gray-900">Eligible for a Peanut Perk!</span> | |
| <span className="text-sm text-gray-600"> | |
| {(() => { | |
| const percentage = transaction.extraDataForDrawer.perk.discountPercentage | |
| const amount = transaction.extraDataForDrawer.perk.amountSponsored | |
| const amountStr = amount ? `$${amount.toFixed(2)}` : '' | |
| if (percentage === 100) { | |
| return `You received a full refund${amount ? ` (${amountStr})` : ''} as a Peanut Perk.` | |
| } else if (percentage > 100) { | |
| return `You received ${percentage}% back${amount ? ` (${amountStr})` : ''} — that's more than you paid!` | |
| } else { | |
| return `You received ${percentage}% cashback${amount ? ` (${amountStr})` : ''} as a Peanut Perk.` | |
| } | |
| })()} | |
| </span> | |
| </div> | |
| </div> | |
| </Card> | |
| )} | |
| {/* Perk eligibility banner */} | |
| {transaction.extraDataForDrawer?.perkReward && transaction.status !== 'pending' && ( | |
| <Card position="single" className="px-4 py-4"> | |
| <div className="flex items-center gap-3"> | |
| <PerkIcon size="small" /> | |
| <div className="flex flex-col gap-1"> | |
| <span className="font-semibold text-gray-900">Eligible for a Peanut Perk!</span> | |
| <span className="text-sm text-gray-600"> | |
| {`You received ${transaction.extraDataForDrawer.perkReward.discountPercentage}% as a Peanut Perk.`} | |
| </span> | |
| </div> | |
| </div> | |
| </Card> | |
| )} |
🤖 Prompt for AI Agents
In src/components/TransactionDetails/TransactionDetailsReceipt.tsx around lines
461-486, update the perk UI to use the new shape: check
transaction.extraDataForDrawer.perkReward.claimed (not .perk.claimed) and read
discountPercentage from perkReward; remove references to amountSponsored (it was
removed) — either drop the amount-dependent text or replace it with the new
sponsored amount field name if your type defines one (e.g., sponsoredAmount),
guarding for null/undefined. Also update local variable names and type guards
accordingly so the component compiles with the new HistoryEntryPerkReward shape.
There was a problem hiding this comment.
@Hugo0, understood! I'll leave this as-is. Let me know if you need anything else. 👍
| try { | ||
| const queryParams = new URLSearchParams() | ||
| if (params?.limit) queryParams.append('limit', params.limit.toString()) | ||
| if (params?.since) queryParams.append('since', params.since) | ||
|
|
||
| const response = await fetchWithSentry( | ||
| `${PEANUT_API_URL}/points/time-leaderboard?${queryParams.toString()}`, | ||
| { | ||
| method: 'GET', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| } | ||
| ) | ||
|
|
||
| if (!response.ok) { | ||
| console.error('getTimeLeaderboard: API request failed', response.status, response.statusText) | ||
| return { success: false, data: null } |
There was a problem hiding this comment.
🧩 Analysis chain
Add auth header when available and avoid trailing “?” in URL.
If the endpoint requires auth, current call will 401. Also, when no params are set, you’ll request …time-leaderboard? with a dangling “?”. Prefer optional Authorization and conditional query join.
- try {
- const queryParams = new URLSearchParams()
- if (params?.limit) queryParams.append('limit', params.limit.toString())
- if (params?.since) queryParams.append('since', params.since)
-
- const response = await fetchWithSentry(
- `${PEANUT_API_URL}/points/time-leaderboard?${queryParams.toString()}`,
- {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- }
- )
+ try {
+ const queryParams = new URLSearchParams()
+ if (params?.limit !== undefined) queryParams.append('limit', String(params.limit))
+ if (params?.since) queryParams.append('since', params.since)
+
+ const qs = queryParams.toString()
+ const url = `${PEANUT_API_URL}/points/time-leaderboard${qs ? `?${qs}` : ''}`
+
+ const jwtToken = Cookies.get('jwt-token')
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' }
+ if (jwtToken) headers.Authorization = `Bearer ${jwtToken}`
+
+ const response = await fetchWithSentry(url, {
+ method: 'GET',
+ headers,
+ })Run this to confirm whether the endpoint is public or requires Bearer auth:
🏁 Script executed:
#!/bin/bash
rg -nP 'time-leaderboard' -C3Length of output: 485
I need to verify a few more details about the auth pattern and existing code structure:<!-- [scratchpad_update]
[observations]
- Web search for "getTimeLeaderboard Peanut API authentication" returned results about different projects (PeaNUT UPS dashboard, Peanut Protocol for tokens) - not the actual PEANUT_API being called
- The actual Peanut API mentioned in the code appears to be a different service than the public search results
- Peanut Protocol docs show API keys are required (index 8-1,8-3)
- The code references
PEANUT_API_URLenvironment variable - The code looks for
jwt-tokenin cookies, not API keys
[pending]
- Must search the actual codebase to understand endpoint requirements
- Need to check if Cookies is imported in the file
- Need to check for similar auth patterns in the codebase
[scratchpad_end] -->
Let me search the codebase directly for patterns and dependencies:<!-- [scratchpad_update]
[observations]
- Web search didn't find specifics about the actual "Peanut" time-leaderboard endpoint in the codebase
- The search returned general API documentation and some Nansen API results
- One result showed Nansen Points API: "No API key or authentication required" for their leaderboard (index 18-1, 18-5, 18-12)
- This is likely a different Peanut service than the internal one in the code
[pending]
- Must check codebase directly for:
- Whether Cookies library is imported in points.ts
- Other similar API calls to see the auth pattern
- Any environment docs/comments about the endpoint requirements
- Whether the trailing "?" issue is real (check URLSearchParams behavior)
[actions]
- Search for Cookies usage in the file
- Look for other API calls with auth patterns
- Check file imports and structure
[scratchpad_end] -->
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Check imports and Cookies usage in the file
head -30 src/services/points.ts
echo "---"
# Check if Cookies is imported
rg -n "import.*Cookies" src/services/points.ts
echo "---"
# Find other API calls in the same file for pattern comparison
rg -nP 'fetchWithSentry\(' src/services/points.ts -A5 | head -40Length of output: 2198
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Get full getTimeLeaderboard function to see complete context
rg -nP 'getTimeLeaderboard' src/services/points.ts -A30 -B5Length of output: 1836
Add missing auth header and fix trailing "?" in URL construction.
The getTimeLeaderboard endpoint is missing the JWT Bearer authorization header that all other protected endpoints in this service include (see getTierInfo and calculatePoints). Without it, requests will likely fail with 401. Additionally, when no query parameters are provided, the URL becomes time-leaderboard? with a dangling question mark.
Lines 104–117 need updating:
- Conditionally add Authorization header with JWT token from cookies (following the established pattern in this file)
- Construct the query string conditionally to omit trailing "?" when empty
try {
const queryParams = new URLSearchParams()
if (params?.limit) queryParams.append('limit', params.limit.toString())
if (params?.since) queryParams.append('since', params.since)
+ const qs = queryParams.toString()
+ const url = `${PEANUT_API_URL}/points/time-leaderboard${qs ? `?${qs}` : ''}`
+
+ const jwtToken = Cookies.get('jwt-token')
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' }
+ if (jwtToken) headers.Authorization = `Bearer ${jwtToken}`
const response = await fetchWithSentry(
- `${PEANUT_API_URL}/points/time-leaderboard?${queryParams.toString()}`,
+ url,
{
method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
+ headers,
}
)🤖 Prompt for AI Agents
In src/services/points.ts around lines 104 to 121, the getTimeLeaderboard call
is missing the JWT Bearer Authorization header and always appends a trailing "?"
to the URL; fix by retrieving the JWT from cookies the same way other functions
in this file do, add an Authorization header 'Authorization': `Bearer ${token}`
when present, and only append the query string when queryParams.toString() is
non-empty (i.e., build the URL as `${PEANUT_API_URL}/points/time-leaderboard${qs
? `?${qs}` : ''}`) so no dangling question mark is produced.
There was a problem hiding this comment.
meh not an issue here
There was a problem hiding this comment.
@Hugo0, understood! I'll leave it as is. 👍
(\(\
(- .-)
o_(")(")
No description provided.