Skip to content

[TASK-15066] feat: implement UI changes for Points#1266

Merged
Zishan-7 merged 11 commits intofeat/pointsfrom
feat/points-tier
Oct 6, 2025
Merged

[TASK-15066] feat: implement UI changes for Points#1266
Zishan-7 merged 11 commits intofeat/pointsfrom
feat/points-tier

Conversation

@Zishan-7
Copy link
Contributor

@Zishan-7 Zishan-7 commented Oct 1, 2025

image

@vercel
Copy link

vercel bot commented Oct 1, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Oct 6, 2025 10:47am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 1, 2025

Warning

Rate limit exceeded

@Zishan-7 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 14 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3859b6d and 9933112.

📒 Files selected for processing (1)
  • src/app/(mobile-ui)/points/page.tsx (4 hunks)

Walkthrough

Adds a points API and types, a confetti utility, and integrates tier/points fetching across invites, mobile points, withdraw, recipient, claim, and payment success UIs; updates invites service to return points-shaped responses and adds canvas-confetti dependency.

Changes

Cohort / File(s) Summary
Points API & types
src/services/points.ts, src/services/services.types.ts
New pointsApi with getTierInfo() and calculatePoints(); adds TierInfo, PointsInvite, PointsInvitesResponse, PointsAction enum, and CalculatePointsRequest types.
Invites service
src/services/invites.ts
getInvites now returns Promise<PointsInvitesResponse>, calls /points/invites, throws on missing JWT/non-OK responses, and returns parsed PointsInvitesResponse.
Mobile points UI
src/app/(mobile-ui)/points/page.tsx
Adds React Query getTierInfo, combined loading guard, error handling; renders tier card and info block; updates invites rendering to use invites.invitees, inviteeId keys, initials from fullName ?? username, verified status from kycStatus, and per-invite totalPoints.
Withdraw bank flow
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
Adds useQuery calling pointsApi.calculatePoints (PointsAction.BRIDGE_TRANSFER) and passes points to DirectSuccessView.
Recipient client flow
src/app/[...recipient]/client.tsx
Adds useQuery calling pointsApi.calculatePoints (PointsAction.P2P_REQUEST_PAYMENT) during CONFIRM for request_pay; threads estimatedPoints into DirectSuccessView.
Claim success view
src/components/Claim/Link/Onchain/Success.view.tsx
Adds useQuery to calculate earned points, shows loading state, triggers confetti on success, and displays earned-points UI.
Payment success view
src/components/Payment/Views/Status.payment.view.tsx
Adds optional points?: number prop to DirectSuccessView, triggers confetti when provided, and conditionally renders earned-points block; updates component signature.
Confetti utility
src/utils/confetti.ts
New client confetti helpers (shootStarConfetti, shootDoubleStarConfetti, confettiPresets) built on canvas-confetti; SSR-guarded and configurable.
Dependency manifest
package.json
Adds runtime dependency canvas-confetti and dev dependency @types/canvas-confetti.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • kushagrasarathe
  • jjramirezn

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "[TASK-15066] feat: implement UI changes for Points" succinctly and accurately reflects the primary focus of the pull request, namely introducing UI enhancements for the Points feature, without unnecessary detail or noise.
Description Check ✅ Passed The pull request description includes a screenshot of the UI changes, which is directly related to the implemented UI updates for the Points feature and thus relevant to the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@notion-workspace
Copy link

@coderabbitai coderabbitai bot added the enhancement New feature or request label Oct 1, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a68369 and 75f2a6a.

📒 Files selected for processing (3)
  • src/app/(mobile-ui)/points/page.tsx (3 hunks)
  • src/services/points.ts (1 hunks)
  • src/services/services.types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/(mobile-ui)/points/page.tsx (5)
src/services/points.ts (1)
  • pointsApi (3-24)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (198-207)
src/components/Global/Card/index.tsx (1)
  • getCardPosition (14-19)
src/components/UserHeader/index.tsx (1)
  • VerifiedUserLabel (34-81)
src/services/points.ts (1)
src/services/services.types.ts (1)
  • TierInfo (327-336)
🔇 Additional comments (1)
src/services/services.types.ts (1)

327-335: TierInfo contract aligns with the new UI usage.
Field names and number types match the downstream consumption in the points page and the mock API.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
src/app/(mobile-ui)/points/page.tsx (2)

64-69: Clamp the tier progress width and handle zero thresholds.

Line 67 divides by tierInfo.data.nextTierThreshold without guarding against zero or undefined values. When a user reaches the maximum tier (threshold = 0 or undefined), this produces Infinity% or NaN%. If totalPoints exceeds the threshold, the progress bar will overflow past 100%.

Apply this diff to add validation and clamping:

                     <div className="flex w-full items-center gap-2">
                         {tierInfo?.data.currentTier}
                         <div className="w-full">
                             <div className="relative h-1.5 w-full overflow-hidden rounded-full bg-grey-2">
-                                <div
-                                    className="h-full rounded-full bg-primary-1 transition-all duration-300"
-                                    style={{
-                                        width: `${(tierInfo.data.totalPoints / tierInfo.data.nextTierThreshold) * 100}%`,
-                                    }}
-                                />
+                                {(() => {
+                                    const threshold = tierInfo.data.nextTierThreshold || 1
+                                    const rawPercent = (tierInfo.data.totalPoints / threshold) * 100
+                                    const progress = Math.min(100, Math.max(0, rawPercent))
+                                    return (
+                                        <div
+                                            className="h-full rounded-full bg-primary-1 transition-all duration-300"
+                                            style={{ width: `${progress}%` }}
+                                        />
+                                    )
+                                })()}
                             </div>
                         </div>
                         {tierInfo?.data.currentTier + 1}
                     </div>

63-69: Clamp the tier progress width and handle zero thresholds.

Line 67 divides by tierInfo.data.nextTierThreshold with no guard. When a user hits the top tier (threshold = 0 or undefined) you'll render Infinity%/NaN%, and if totalPoints ever overshoots the threshold the bar will overflow past 100%. Please derive the percentage with a zero check and clamp to [0, 100].

Apply this diff to fix the calculation:

-                                <div
-                                    className="h-full rounded-full bg-primary-1 transition-all duration-300"
-                                    style={{
-                                        width: `${(tierInfo.data.totalPoints / tierInfo.data.nextTierThreshold) * 100}%`,
-                                    }}
-                                />
+                                {(() => {
+                                    const threshold = tierInfo.data.nextTierThreshold || 0
+                                    const rawPercent = threshold > 0 ? (tierInfo.data.totalPoints / threshold) * 100 : 100
+                                    const progress = Math.min(100, Math.max(0, rawPercent))
+                                    return (
+                                        <div
+                                            className="h-full rounded-full bg-primary-1 transition-all duration-300"
+                                            style={{ width: `${progress}%` }}
+                                        />
+                                    )
+                                })()}
🧹 Nitpick comments (1)
src/services/points.ts (1)

7-26: Add explicit guard for missing JWT token.

The method retrieves the JWT token but does not explicitly check if it's undefined before making the request. If the token is missing, the Authorization header will be "Bearer undefined", which the backend will reject. While the error is caught by the response.ok check or the catch block, adding an explicit guard improves clarity and aligns with the updated invitesApi.getInvites (which now throws on missing token in lines 31-33 of invites.ts).

Apply this diff to add an explicit guard:

 getTierInfo: async (): Promise<{ success: boolean; data: TierInfo | null }> => {
     try {
         const jwtToken = Cookies.get('jwt-token')
+        if (!jwtToken) {
+            return { success: false, data: null }
+        }
         const response = await fetchWithSentry(`${PEANUT_API_URL}/points`, {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75f2a6a and e84ad24.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/points/page.tsx (5 hunks)
  • src/services/invites.ts (2 hunks)
  • src/services/points.ts (1 hunks)
  • src/services/services.types.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/services/services.types.ts (1)
src/utils/bridge-accounts.utils.ts (1)
  • BridgeKycStatus (34-34)
src/services/points.ts (3)
src/services/services.types.ts (1)
  • TierInfo (327-336)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (1)
  • PEANUT_API_URL (43-47)
src/services/invites.ts (3)
src/services/services.types.ts (1)
  • PointsInvitesResponse (352-362)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (1)
  • PEANUT_API_URL (43-47)
src/app/(mobile-ui)/points/page.tsx (6)
src/services/points.ts (1)
  • pointsApi (6-27)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (198-207)
src/services/services.types.ts (1)
  • PointsInvite (338-350)
src/components/Global/Card/index.tsx (1)
  • getCardPosition (14-19)
src/components/UserHeader/index.tsx (1)
  • VerifiedUserLabel (34-81)
🔇 Additional comments (11)
src/services/services.types.ts (4)

327-362: LGTM! Type definitions are well-structured.

The new interfaces (TierInfo, PointsInvite, PointsInvitesResponse) are clearly defined with appropriate nullable fields and align correctly with their usage across the services and UI components.


327-336: LGTM!

The TierInfo interface is well-structured and correctly typed for tier-related data. All fields are appropriately named and typed.


338-350: LGTM!

The PointsInvite interface is well-structured with appropriate nullable fields (fullName, kycStatus) and correctly typed for detailed invite data.


352-362: LGTM!

The PointsInvitesResponse interface is well-structured with a nested summary object and correctly typed array of invitees.

src/app/(mobile-ui)/points/page.tsx (6)

39-45: LGTM! Loading guards are properly implemented.

The dual loading check (Line 39) and the data guard (Line 43-45) correctly prevent rendering the UI until both tier info and invites data are available, avoiding potential runtime errors.


97-138: LGTM! Invites rendering correctly uses the new data shape.

The code properly maps over invites.invitees (Line 111), uses the PointsInvite type fields (username, fullName, kycStatus, totalPoints), and implements the fullName fallback for avatar initials (Line 120).


30-45: LGTM!

The tierInfo query setup and loading guards are correct. The component waits for both queries to complete and guards against missing data before rendering.


80-86: LGTM!

The informational block is well-placed and provides helpful context about the points system.


97-138: LGTM!

The invites rendering correctly uses the new PointsInvite data shape, with appropriate fallback for fullName in avatar initials and correct display of totalPoints.


140-161: LGTM!

The empty invites state correctly checks invites.invitees.length and provides a helpful call-to-action.

src/services/invites.ts (1)

31-33: LGTM!

The explicit error handling (throwing on missing token and non-OK responses) improves error visibility and aligns with typical React Query patterns. This allows consumers to handle failures explicitly via error states.

Also applies to: 42-44, 48-50

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/services/points.ts (1)

7-26: Add JWT validation and error logging.

Lines 9-10 send Authorization: Bearer undefined if the JWT token is missing, which may cause unexpected server behavior. Additionally, line 23 silently swallows all errors without logging, making debugging difficult.

Apply this diff to add validation and logging:

     getTierInfo: async (): Promise<{ success: boolean; data: TierInfo | null }> => {
         try {
             const jwtToken = Cookies.get('jwt-token')
+            if (!jwtToken) {
+                console.warn('No JWT token found for getTierInfo')
+                return { success: false, data: null }
+            }
             const response = await fetchWithSentry(`${PEANUT_API_URL}/points`, {
                 method: 'GET',
                 headers: {
                     Authorization: `Bearer ${jwtToken}`,
                     'Content-Type': 'application/json',
                 },
             })
             if (!response.ok) {
                 return { success: false, data: null }
             }

             const pointsInfo: TierInfo = await response.json()
             return { success: true, data: pointsInfo }
-        } catch {
+        } catch (error) {
+            console.error('Failed to fetch tier info:', error)
             return { success: false, data: null }
         }
     },
🧹 Nitpick comments (2)
src/services/points.ts (1)

65-66: Add error logging to calculatePoints.

The catch block at lines 65-66 swallows the original error and throws a generic message, losing valuable debugging information about what actually failed (network error, JSON parse error, auth failure, etc.).

Apply this diff to preserve error context:

-        } catch {
-            throw new Error('Failed to calculate points')
+        } catch (error) {
+            console.error('Failed to calculate points:', error)
+            throw error instanceof Error ? error : new Error('Failed to calculate points')
         }
src/utils/confetti.ts (1)

24-47: Add reduced-motion support to respect user preferences.

The confetti implementation should respect users' prefers-reduced-motion setting. Based on learnings, canvas-confetti@1.9.x provides a disableForReducedMotion option that should be enabled.

Apply this diff to add reduced-motion support:

 const defaultConfettiConfig = {
     spread: 360,
     ticks: 80,
     gravity: 0.3,
     decay: 0.96,
     startVelocity: 15,
     colors: ['#FFE400', '#FFBD00', '#E89400', '#FFCA6C', '#FDFFB8'],
+    disableForReducedMotion: true,
 }

Based on learnings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e84ad24 and cb23e54.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • package.json (2 hunks)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (3 hunks)
  • src/app/[...recipient]/client.tsx (3 hunks)
  • src/components/Claim/Link/Onchain/Success.view.tsx (6 hunks)
  • src/components/Payment/Views/Status.payment.view.tsx (5 hunks)
  • src/services/points.ts (1 hunks)
  • src/services/services.types.ts (1 hunks)
  • src/utils/confetti.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
📚 Learning: 2025-09-18T09:30:42.901Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1230
File: src/app/(mobile-ui)/withdraw/page.tsx:92-97
Timestamp: 2025-09-18T09:30:42.901Z
Learning: In src/app/(mobile-ui)/withdraw/page.tsx, the useEffect that calls setShowAllWithdrawMethods(true) when amountFromContext exists is intentionally designed to run only on component mount (empty dependency array), not when amountFromContext changes. This is the correct behavior for the withdraw flow where showing all methods should only happen on initial load when an amount is already present.

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
🧬 Code graph analysis (6)
src/components/Payment/Views/Status.payment.view.tsx (1)
src/utils/confetti.ts (1)
  • shootDoubleStarConfetti (49-67)
src/services/services.types.ts (1)
src/utils/bridge-accounts.utils.ts (1)
  • BridgeKycStatus (34-34)
src/components/Claim/Link/Onchain/Success.view.tsx (4)
src/services/points.ts (1)
  • pointsApi (6-69)
src/utils/general.utils.ts (1)
  • formatTokenAmount (444-465)
src/utils/confetti.ts (1)
  • shootDoubleStarConfetti (49-67)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/app/[...recipient]/client.tsx (1)
src/services/points.ts (1)
  • pointsApi (6-69)
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)
src/services/points.ts (1)
  • pointsApi (6-69)
src/services/points.ts (3)
src/services/services.types.ts (2)
  • TierInfo (327-336)
  • CalculatePointsRequest (373-377)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (1)
  • PEANUT_API_URL (43-47)
🔇 Additional comments (3)
package.json (1)

54-54: LGTM!

The canvas-confetti dependency and its types are properly added with current stable versions (^1.9.3 runtime, ^1.9.0 types). Based on learnings, version 1.9.x includes stability fixes and reduced-motion support.

Also applies to: 96-96

src/components/Payment/Views/Status.payment.view.tsx (1)

42-42: LGTM!

The points integration is well-implemented:

  • Optional prop with proper typing
  • Confetti triggered only when points are present
  • Clean conditional rendering of the points UI

Also applies to: 170-174, 244-249

src/services/services.types.ts (1)

327-377: LGTM!

The new points-related types are well-structured:

  • Clear interface definitions with appropriate field types
  • Proper reuse of existing types (BridgeKycStatus)
  • Descriptive enum values for PointsAction
  • Good separation of concerns between request/response types

@Zishan-7 Zishan-7 marked this pull request as ready for review October 2, 2025 15:33
@Zishan-7 Zishan-7 changed the title [TASK-15066] feat: implement UI changes [TASK-15066] feat: implement UI changes for Points Oct 2, 2025
@Zishan-7 Zishan-7 requested a review from Hugo0 October 2, 2025 15:33
@notion-workspace
Copy link

1 similar comment
@notion-workspace
Copy link

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (2)
src/services/points.ts (1)

7-30: Add error logging in the catch block.

Line 27 silently swallows all errors (JSON parse failures, network errors not caught by fetchWithSentry, etc.), making debugging difficult in production. While fetchWithSentry handles network errors, the catch block here can catch JSON parsing errors and other unexpected failures.

Apply this diff:

-        } catch {
+        } catch (error) {
+            console.error('Failed to fetch tier info:', error)
             return { success: false, data: null }
         }
src/components/Claim/Link/Onchain/Success.view.tsx (1)

39-56: Fix the unstable queryKey that defeats caching.

Line 39 uses crypto.randomUUID() inside the useMemo dependency array, which generates a new UUID on every render. This causes the query key to change constantly, defeating React Query's caching and triggering unnecessary refetches.

Apply this diff to use a stable queryKey:

-    const queryKey = useMemo(() => ['calculate-points'], [crypto.randomUUID()])
+    const queryKey = useMemo(
+        () => ['calculate-points', 'claim-success', claimLinkData.link, claimLinkData.senderAddress],
+        [claimLinkData.link, claimLinkData.senderAddress]
+    )
🧹 Nitpick comments (1)
src/services/points.ts (1)

32-72: Preserve error details in the catch block.

Line 69-71 catches all errors but throws a generic "Failed to calculate points" message, losing the original error context (network failures, JSON parse errors, etc.). This makes debugging production issues difficult.

Apply this diff to log the original error:

-        } catch {
-            throw new Error('Failed to calculate points')
+        } catch (error) {
+            console.error('Failed to calculate points:', error)
+            throw error instanceof Error ? error : new Error('Failed to calculate points')
         }

Alternatively, if you want to always throw the same error message for security reasons, at least log the original error:

-        } catch {
-            throw new Error('Failed to calculate points')
+        } catch (error) {
+            console.error('Failed to calculate points:', error)
+            throw new Error('Failed to calculate points')
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb23e54 and 50ea224.

📒 Files selected for processing (4)
  • src/app/[...recipient]/client.tsx (3 hunks)
  • src/components/Claim/Link/Onchain/Success.view.tsx (6 hunks)
  • src/services/invites.ts (2 hunks)
  • src/services/points.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-02T15:23:01.405Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1266
File: src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:46-57
Timestamp: 2025-10-02T15:23:01.405Z
Learning: In the withdraw flow at src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, the points calculation query intentionally uses crypto.randomUUID() in the queryKey dependency array to bypass React Query caching, ensuring fresh points estimates on every render. This is the intended behavior.

Applied to files:

  • src/components/Claim/Link/Onchain/Success.view.tsx
  • src/app/[...recipient]/client.tsx
🧬 Code graph analysis (4)
src/components/Claim/Link/Onchain/Success.view.tsx (4)
src/services/points.ts (1)
  • pointsApi (6-73)
src/utils/general.utils.ts (1)
  • formatTokenAmount (444-465)
src/utils/confetti.ts (1)
  • shootDoubleStarConfetti (49-67)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/services/points.ts (3)
src/services/services.types.ts (2)
  • TierInfo (327-336)
  • CalculatePointsRequest (373-377)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (1)
  • PEANUT_API_URL (43-47)
src/services/invites.ts (3)
src/services/services.types.ts (1)
  • PointsInvitesResponse (352-362)
src/utils/sentry.utils.ts (1)
  • fetchWithSentry (26-104)
src/constants/general.consts.ts (1)
  • PEANUT_API_URL (43-47)
src/app/[...recipient]/client.tsx (1)
src/services/points.ts (1)
  • pointsApi (6-73)
🔇 Additional comments (5)
src/services/invites.ts (2)

5-5: LGTM!

The import change correctly reflects the new return type structure for getInvites.


28-50: Past review concerns addressed.

The unsafe .catch(() => ({})) fallback that was flagged in previous reviews has been removed. The method now properly propagates JSON parse errors to the outer catch block, preventing runtime crashes from invalid empty objects. The throwing behavior is appropriate for an API method, and React Query (used by callers) will handle these errors correctly.

src/app/[...recipient]/client.tsx (1)

36-38: LGTM: Import additions are correct.

The imports for useQuery, pointsApi, and PointsAction are necessary and properly used in the points calculation query below.

src/components/Claim/Link/Onchain/Success.view.tsx (2)

1-1: LGTM: Import additions are correct.

The new imports (useQuery, STAR_STRAIGHT_ICON, pointsApi, PointsAction, PeanutLoading, and shootDoubleStarConfetti) are all necessary for the points calculation and display features added below.

Also applies to: 12-12, 19-24


144-148: LGTM: Confetti trigger is correctly conditional.

The confetti effect now correctly triggers only when pointsData?.estimatedPoints is available, addressing the previous concern about unconditional triggering.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/app/(mobile-ui)/points/page.tsx (1)

63-69: Division by zero and overflow issue remains unaddressed.

The progress bar calculation still divides by nextTierThreshold without guards. This is a duplicate of a previous review comment that has not been resolved.

When nextTierThreshold is 0 or undefined (e.g., at max tier), the width will be Infinity% or NaN%. If totalPoints exceeds nextTierThreshold, the bar will overflow past 100%.

Apply this diff to fix:

-                                <div
-                                    className="h-full rounded-full bg-primary-1 transition-all duration-300"
-                                    style={{
-                                        width: `${(tierInfo.data.totalPoints / tierInfo.data.nextTierThreshold) * 100}%`,
-                                    }}
-                                />
+                                {(() => {
+                                    const threshold = tierInfo.data.nextTierThreshold || 1
+                                    const rawPercent = (tierInfo.data.totalPoints / threshold) * 100
+                                    const progress = Math.min(100, Math.max(0, rawPercent))
+                                    return (
+                                        <div
+                                            className="h-full rounded-full bg-primary-1 transition-all duration-300"
+                                            style={{ width: `${progress}%` }}
+                                        />
+                                    )
+                                })()}
🧹 Nitpick comments (1)
src/app/(mobile-ui)/points/page.tsx (1)

52-78: Consider max tier edge case.

The UI displays currentTier + 1 as the next tier (line 72) and shows "points needed for the next tier" (line 76). When users reach the maximum tier, these elements may display misleading information.

Consider conditionally rendering the progress bar and next tier message only when a next tier exists, or display alternative content for max tier users.

Example:

{tierInfo.data.nextTierThreshold > 0 ? (
    // Current progress bar and next tier UI
) : (
    <p className="text-sm text-grey-1">You've reached the maximum tier!</p>
)}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50ea224 and a1b5a6d.

📒 Files selected for processing (2)
  • src/app/(mobile-ui)/points/page.tsx (5 hunks)
  • src/app/[...recipient]/client.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/[...recipient]/client.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(mobile-ui)/points/page.tsx (5)
src/services/points.ts (1)
  • pointsApi (6-73)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (198-207)
src/services/services.types.ts (1)
  • PointsInvite (338-350)
src/components/Global/Card/index.tsx (1)
  • getCardPosition (14-19)
🔇 Additional comments (5)
src/app/(mobile-ui)/points/page.tsx (5)

14-14: LGTM!

The new imports are properly utilized throughout the component.

Also applies to: 17-19


30-34: LGTM!

The tier info query follows best practices with proper key structure and enablement condition.


39-45: LGTM!

The loading state handling is thorough. The additional tierInfo?.data check (lines 43-45) is technically redundant after the loading check but provides extra safety for data availability.


80-86: LGTM!

The informational section provides clear guidance to users about the points system.


97-161: LGTM!

The invites rendering correctly uses the new PointsInvite type structure with appropriate optional chaining, proper key usage (inviteeId), and correct derivation of verification status from kycStatus.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1b5a6d and f55d538.

📒 Files selected for processing (2)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (3 hunks)
  • src/components/Claim/Link/Onchain/Success.view.tsx (6 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-10-02T15:23:01.513Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1266
File: src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:46-57
Timestamp: 2025-10-02T15:23:01.513Z
Learning: In the withdraw flow at src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, the points calculation query intentionally uses crypto.randomUUID() in the queryKey dependency array to bypass React Query caching, ensuring fresh points estimates on every render. This is the intended behavior.

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/components/Claim/Link/Onchain/Success.view.tsx
📚 Learning: 2025-05-22T15:38:48.586Z
Learnt from: kushagrasarathe
PR: peanutprotocol/peanut-ui#869
File: src/app/(mobile-ui)/withdraw/page.tsx:82-88
Timestamp: 2025-05-22T15:38:48.586Z
Learning: The country-specific withdrawal route exists at src/app/(mobile-ui)/withdraw/[...country]/page.tsx and renders the AddWithdrawCountriesList component with flow="withdraw".

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
📚 Learning: 2025-09-18T09:30:42.901Z
Learnt from: Zishan-7
PR: peanutprotocol/peanut-ui#1230
File: src/app/(mobile-ui)/withdraw/page.tsx:92-97
Timestamp: 2025-09-18T09:30:42.901Z
Learning: In src/app/(mobile-ui)/withdraw/page.tsx, the useEffect that calls setShowAllWithdrawMethods(true) when amountFromContext exists is intentionally designed to run only on component mount (empty dependency array), not when amountFromContext changes. This is the correct behavior for the withdraw flow where showing all methods should only happen on initial load when an amount is already present.

Applied to files:

  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
🧬 Code graph analysis (2)
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)
src/services/points.ts (1)
  • pointsApi (6-73)
src/components/Claim/Link/Onchain/Success.view.tsx (4)
src/services/points.ts (1)
  • pointsApi (6-73)
src/utils/general.utils.ts (1)
  • formatTokenAmount (444-465)
src/utils/confetti.ts (1)
  • shootDoubleStarConfetti (49-67)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (1)
src/app/(mobile-ui)/points/page.tsx (1)

80-85: Clamp the tier progress width and handle zero thresholds.

Line 83 divides by tierInfo.data.nextTierThreshold with no guard. When a user hits the top tier (threshold = 0 or undefined) you'll render Infinity%/NaN%, and if totalPoints ever overshoots the threshold the bar will overflow past 100%.

Apply this diff to fix the calculation:

-                                <div
-                                    className="h-full rounded-full bg-primary-1 transition-all duration-300"
-                                    style={{
-                                        width: `${(tierInfo.data.totalPoints / tierInfo.data.nextTierThreshold) * 100}%`,
-                                    }}
-                                />
+                                {(() => {
+                                    const threshold = tierInfo.data.nextTierThreshold || 0
+                                    const rawPercent = threshold > 0 ? (tierInfo.data.totalPoints / threshold) * 100 : 100
+                                    const progress = Math.min(100, Math.max(0, rawPercent))
+                                    return (
+                                        <div
+                                            className="h-full rounded-full bg-primary-1 transition-all duration-300"
+                                            style={{ width: `${progress}%` }}
+                                        />
+                                    )
+                                })()}
🧹 Nitpick comments (1)
src/app/(mobile-ui)/points/page.tsx (1)

114-114: Simplify the redundant conditional check.

The condition invites?.invitees?.length && invites.invitees.length > 0 is redundant. If invites?.invitees?.length is truthy, it already implies > 0.

Apply this diff to simplify:

-                {invites?.invitees?.length && invites.invitees.length > 0 && (
+                {invites?.invitees && invites.invitees.length > 0 && (
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f55d538 and 3859b6d.

📒 Files selected for processing (1)
  • src/app/(mobile-ui)/points/page.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(mobile-ui)/points/page.tsx (9)
src/context/authContext.tsx (1)
  • useAuth (182-188)
src/services/invites.ts (1)
  • invitesApi (7-89)
src/services/points.ts (1)
  • pointsApi (6-73)
src/components/Global/PeanutLoading/index.tsx (1)
  • PeanutLoading (4-19)
src/components/Global/EmptyStates/EmptyState.tsx (1)
  • EmptyState (13-28)
src/components/Global/Icons/Icon.tsx (1)
  • Icon (198-207)
src/services/services.types.ts (1)
  • PointsInvite (338-350)
src/components/Global/Card/index.tsx (1)
  • getCardPosition (14-19)
src/components/UserHeader/index.tsx (1)
  • VerifiedUserLabel (34-81)
🔇 Additional comments (2)
src/app/(mobile-ui)/points/page.tsx (2)

48-50: LGTM!

The loading state correctly guards against both queries and returns the appropriate loading component.


97-103: LGTM!

The info block clearly explains the points system to users.

@Zishan-7 Zishan-7 merged commit 115ae03 into feat/points Oct 6, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants