Skip to content

Multi subscriptions#1612

Merged
prxt6529 merged 89 commits intomainfrom
multi-subscriptions
Dec 19, 2025
Merged

Multi subscriptions#1612
prxt6529 merged 89 commits intomainfrom
multi-subscriptions

Conversation

@prxt6529
Copy link
Copy Markdown
Collaborator

@prxt6529 prxt6529 commented Nov 20, 2025

Summary by CodeRabbit

  • New Features

    • Edition‑preference toggle; redesigned Top‑Up with preset + “Other” modal; Confirm Token ID modal; Upload Photos and Automatic‑Airdrops CSV modals; public‑subscriptions view and per‑section pagination.
  • Style

    • List/grid layout refresh, tw‑prefixed spacing, circular loaders, updated icons/buttons, “drop” wording, and UTC last‑update timestamp in headers.
  • Bug Fixes

    • Improved validation, clearer toasts, correct loading/disabled states, modal escape/keyboard behavior, NotFound/empty‑state handling.
  • Tests

    • Expanded coverage for token confirmation, uploads, public subscriptions, top‑up and subscription flows.

✏️ Tip: You can customize this high-level summary in your review settings.

Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 20, 2025

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

💤 Files selected but had no reviewable changes (1)
  • openapi.yaml
⛔ Files ignored due to path filters (1)
  • generated/models/NFTFinalSubscription.ts is excluded by !**/generated/**

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Refactors subscription and distribution UIs: replaces legacy subscription types with generated models, adds edition-preference toggle, reworks top‑up and modal flows (confirm token, upload photos, automatic airdrops) with context wiring, adds helpers/constants, updates styles, and expands tests.

Changes

Cohort / File(s) Change Summary
Subscriptions — layout & styling
components/user/subscriptions/UserPageSubscriptions.tsx, components/user/subscriptions/UserPageSubscriptions.module.scss, components/user/subscriptions/UserPageSubscriptionsMode.tsx, components/user/subscriptions/UserPageSubscriptionsAirdropAddress.tsx, components/user/subscriptions/UserPageSubscriptionsBalance.tsx
Grid-based layout refactor, tw-* class conversions, Mode header shows UTC last_update, Spinner→CircleLoader, raw airdrop address + edit icon, CSS class renames, and switch to generated-model types.
Edition preference
components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
New component: subscribe_all_editions toggle with auth flow, POST API call, loading state, toasts, and refresh callback.
Top‑up flow & card options
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
Replaced CardCountTopup → CardCountOption (signature change); modal/portal flow, CircleLoader, preset + "Other" option support, ETH calculation/validation, transaction modal states and links.
Upcoming subscriptions list
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
Table→list UI, per-item selectedCount state, adds eligibilityCount prop, POST subscription-count updates, toasts, phase/position/address formatting, and query invalidation.
History & pagination
components/user/subscriptions/UserPageSubscriptionsHistory.tsx, components/pagination/Pagination.tsx
Per-section accordions for Redeemed/Logs/Topups, Page typed props, per-section pagination handlers, and minor presentation tweaks.
Entities → generated models
entities/ISubscription.ts, entities/IDistribution.ts, components/subscriptions-report/SubscriptionsReport.tsx
Removed legacy interfaces from ISubscription, migrated imports to generated models; added spots_airdrop and spots_allowlist to DistributionPhaseEntry.
Distribution & minting
components/distribution/Distribution.tsx, components/manifoldMinting/ManifoldMinting.tsx
NFT id validation with NotFound/UpcomingMemePage, per-phase spots rendering and Airdrops row, layout/pagination cleanup, and minor image/prop fixes.
Mint claim error handling
hooks/useManifoldClaim.ts, components/mint-countdown-box/MemePageMintCountdown.tsx, components/mint-countdown-box/MintCountdownBox.tsx
Add isError to ManifoldClaim and MintCountdownBox props; propagate and render an error branch in mint UI.
Helpers & constants
helpers/Helpers.ts, components/distribution-plan-tool/review-distribution-plan/table/constants.ts
Add extractAllNumbers() and isValidPositiveInteger() helpers; add PUBLIC_SUBSCRIPTIONS_PHASE_ID constant.
Distribution Plan Tool — context
components/distribution-plan-tool/DistributionPlanToolContext.tsx
Add confirmedTokenId and setConfirmedTokenId to context and provider state.
Distribution Plan Tool — footer & modals
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx, ...ConfirmTokenId.tsx, ...UploadPhotos.tsx, ...AutomaticAirdrops.tsx
New ConfirmTokenIdModal, UploadDistributionPhotosModal, AutomaticAirdropsModal; overview refresh and admin actions (upload/reset/finalize), CSV parsing/validation, toasts, and confirmedTokenId wiring.
Review table & snapshots
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx, ...Row.tsx, ...Subscription.tsx, create-snapshots/table/CreateSnapshotTableHeader.tsx, create-snapshots/table/CreateSnapshotTableRow.tsx
Rework phase/row construction, add COMPONENT item type, support PUBLIC_SUBSCRIPTIONS_PHASE_ID, guard public-phase actions, add Status/Contract headers and downloader status badge.
Rounded icon buttons
components/distribution-plan-tool/common/RoundedCsvIconButton.tsx, components/distribution-plan-tool/common/RoundedJsonIconButton.tsx, components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx
Add optional disabled?: boolean prop; buttons disabled/styled when `disabled
Tests
__tests__/components/distribution-plan-tool/*.test.tsx, __tests__/components/distribution/Distribution.test.tsx, __tests__/components/user/subscriptions/*.test.tsx, __tests__/components/distribution-plan-tool/common/RoundedManifoldIconButton.test.tsx
Add/adjust tests for ConfirmTokenIdModal, upload/CSV flows, public vs admin behavior, NotFound/UpcomingMemePage mocks, CircleLoader/wagmi/react-query mocks, and updated import patterns.
Global styles & layout
styles/globals.scss, components/user/layout/UserPageLayout.tsx, src/components/user/user-page-header/UserPageHeader.tsx
react-toggle hover/checked rules and SCSS formatting; UserPageLayout switched to flex column; UserPageHeader now accepts fallbackMainAddress prop.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Admin as Admin/User
    participant UI as Review UI (Footer)
    participant Ctx as DistributionPlanToolContext
    participant Modal as Confirm/Upload Modal
    participant API as Server (commonApi)

    Admin->>UI: trigger admin action (Finalize / Upload Photos / Automatic Airdrops)
    UI->>Ctx: read confirmedTokenId
    alt confirmedTokenId missing
        UI->>Modal: open ConfirmTokenIdModal
        Modal->>Admin: prompt for tokenId
        Admin->>Modal: submit tokenId
        Modal->>Ctx: setConfirmedTokenId(tokenId)
    end
    UI->>Modal: open Upload/AutomaticAirdrops modal
    Modal->>Admin: accept file/CSV + tokenId
    Admin->>Modal: submit payload
    Modal->>API: POST upload/normalize(contract, tokenId, payload)
    API-->>Modal: response (success/error)
    Modal->>UI: close and update state
    UI->>Admin: show toast and refresh overview
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

  • Areas to focus review on:
    • ConfirmTokenIdModal ↔ DistributionPlanToolContext confirmedTokenId lifecycle and propagation.
    • CSV parsing/validation, file-size limits, and error handling in AutomaticAirdropsModal and UploadDistributionPhotosModal.
    • CardCountOption signature change and top‑up transaction value calculations (and corresponding tests).
    • Migration from entities/ISubscription to generated models — ensure all imports/types were updated consistently.
    • Admin vs public gating across review table/footer using PUBLIC_SUBSCRIPTIONS_PHASE_ID.
    • Tests and mocks consistency (CircleLoader, wagmi, react-query's useQueryClient).

Possibly related PRs

"🐇
I hopped through modals, token crumbs in paw,
Parsed CSV gardens and fixed a small bug gnaw,
Toggles flipped, photos tucked, toasts gleamed in light,
Counts lined up neat and buttons snugly tight,
Hooray — subscriptions hum, cozy through the night!"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Multi subscriptions' is vague and does not clearly convey the specific changes made in this large, multi-faceted PR. Consider using a more descriptive title that highlights the primary change, such as 'Refactor subscriptions components to use generated models and modernize UI' or 'Migrate subscriptions to new model imports and update layout structure'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/user/subscriptions/UserPageSubscriptionsMode.tsx (1)

33-82: Critical: Toggle state is not persisted to the backend.

The toggleMode function only updates local state (lines 38-40) without making the API call to persist changes. This means the toggle will appear to work in the UI but the setting won't be saved, causing data loss when the page refreshes or the component remounts.

The commented-out API logic (lines 41-81) shows the intended implementation. Do you want me to help uncomment and verify this logic, or is there a tracking issue for implementing this TODO?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ae05e8 and 43cf280.

📒 Files selected for processing (3)
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx (3 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1 hunks)
  • styles/globals.scss (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/user/subscriptions/UserPageSubscriptionsMode.tsx (1)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

143-144: LGTM! Formatting change with no functional impact.

The Link attributes are now on separate lines, improving readability while maintaining the same behavior.

components/user/subscriptions/UserPageSubscriptionsMode.tsx (3)

4-6: LGTM! CircleLoader import added for loading indicators.

The CircleLoader component and size enum are properly imported to replace the previous spinner implementation.


25-27: LGTM! State variables added for Edition Preference toggle.

The new state variables properly track the edition preference mode and its loading state.


131-172: LGTM! Edition Preference section properly gated behind Automatic mode.

The conditional rendering ensures the Edition Preference controls only appear when Automatic mode is active, which aligns with the intended two-tier UI design.

styles/globals.scss (2)

22-25: LGTM! Formatting improvement for layout calculation.

The --layout-margin calculation is reformatted to multi-line for better readability while maintaining the same computed value.


687-697: LGTM! New visual states added for toggle components.

The hover and checked states for .react-toggle enhance the UI feedback for the new two-tier toggle system introduced in UserPageSubscriptionsMode.tsx.

Comment thread components/user/subscriptions/UserPageSubscriptionsMode.tsx Outdated
Comment thread components/user/subscriptions/UserPageSubscriptionsMode.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (5)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (2)

307-311: Use Number.parseInt for consistency.

Per static analysis, prefer Number.parseInt over the global parseInt.

                    onChange={(e) => {
-                      const value = parseInt(e.target.value, 10);
+                      const value = Number.parseInt(e.target.value, 10);
                       setSelectedCount(value);
                       handleUpdateSubscriptionCount(value);
                     }}

298-321: Consider the UX of immediate API calls on selection change.

Every dropdown selection triggers an API call immediately. If users accidentally select the wrong value, they'll experience unnecessary network requests and potential brief UI flickers. Consider adding a confirmation button or debouncing, though this may be acceptable depending on UX requirements.

components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (3)

27-35: Simplify subscriptionEligibilityCount computation and confirm default semantics

Two points here:

  1. useMemo is unnecessary for such a cheap derivation; a simple computed constant per render is enough and clearer:
-  const subscriptionEligibilityCount = useMemo<number>(
-    () => props.details?.subscription_eligibility_count ?? 1,
-    [props.details?.subscription_eligibility_count]
-  );
+  const subscriptionEligibilityCount =
+    props.details?.subscription_eligibility_count ?? 1;
  1. Please confirm that falling back to 1 when details is undefined (or the field is missing) is intentional and matches backend semantics; in some flows, 0 or omitting the badge entirely might be more accurate.

32-35: Effect dependency choice is acceptable but relies on details identity

useEffect depends on props.details and resets isAllEditions from props.details?.subscribe_all_editions. This is fine if upstream always provides a new details object when data changes (typical for API responses). If upstream mutates an existing object in place, this effect would not re-run when subscribe_all_editions flips.

If you expect in-place mutation, consider depending on the specific field instead:

-  useEffect(() => {
-    setIsAllEditions(props.details?.subscribe_all_editions ?? false);
-  }, [props.details]);
+  useEffect(() => {
+    setIsAllEditions(props.details?.subscribe_all_editions ?? false);
+  }, [props.details?.subscribe_all_editions]);

Otherwise current approach is fine.


82-127: Toggle UI is clear; consider small UX/a11y refinements

The layout, readonly handling, and loader placement are all good. A couple of optional polish points:

  • Accessible labeling: With two <label> elements pointing to the same id, some screen readers may read both texts as the control’s name (e.g., “One edition All eligible”). You might consider:

    • Using a single label as the accessible name, or
    • Adding an explicit aria-label / aria-labelledby to control exactly what’s announced.
  • Helper text association: The explanatory line (“You will receive…”) could be associated with the toggle via aria-describedby for better screen-reader context if you want to go the extra mile.

These are not blockers but would tighten accessibility.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43cf280 and f3f40a9.

📒 Files selected for processing (6)
  • components/user/subscriptions/UserPageSubscriptions.tsx (2 hunks)
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx (3 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (2 hunks)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (6 hunks)
  • entities/ISubscription.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
components/user/subscriptions/UserPageSubscriptionsMode.tsx (1)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
components/user/subscriptions/UserPageSubscriptions.tsx (2)
components/user/subscriptions/UserPageSubscriptionsAirdropAddress.tsx (1)
  • UserPageSubscriptionsAirdropAddress (17-67)
components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (1)
  • UserPageSubscriptionsEditionPreference (13-129)
🪛 GitHub Check: SonarCloud Code Analysis
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx

[warning] 196-196: Change the argument of this setter to not use its matching state variable

See more on https://sonarcloud.io/project/issues?id=6529-Collections_6529seize-frontend&issues=AZq6CKwZGPDYetru4taF&open=AZq6CKwZGPDYetru4taF&pullRequest=1612


[warning] 308-308: Prefer Number.parseInt over parseInt.

See more on https://sonarcloud.io/project/issues?id=6529-Collections_6529seize-frontend&issues=AZq6CKwZGPDYetru4taG&open=AZq6CKwZGPDYetru4taG&pullRequest=1612

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (2)

143-144: LGTM!

The attribute formatting change is purely cosmetic and maintains correct behavior.


256-263: LGTM!

The tw-text-iron-400 class addition provides consistent styling for the "Show More" button section.

entities/ISubscription.ts (2)

6-7: Verify API backward compatibility for new required fields.

The new fields subscribe_all_editions and subscription_eligibility_count are non-optional. Ensure all API responses always include these fields to prevent runtime errors. If the backend might omit them for existing data, consider making them optional with ?.


23-23: Same consideration for subscribed_count.

Verify the API always returns this field. The consuming code in UserPageSubscriptionsUpcoming.tsx (line 132) handles the fallback with ?? 1, suggesting it might be optional. If so, mark it as subscribed_count?: number.

components/user/subscriptions/UserPageSubscriptionsMode.tsx (2)

84-97: LGTM!

The timestamp display logic correctly guards against undefined/zero values and uses consistent UTC formatting with clear locale options.


101-117: LGTM!

The layout migration to Tailwind utility classes is clean. The CircleLoader provides a better loading indicator than DotLoader and is correctly sized with CircleLoaderSize.MEDIUM.

components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (2)

131-140: LGTM!

The subscribedCount memoization and selectedCount state synchronization are correctly implemented, with proper fallback to 1 when the API field is missing.


207-253: LGTM!

The handleUpdateSubscriptionCount function follows the established patterns: proper auth check, optimistic updates with rollback on failure, and toast notifications. Error handling correctly reverts selectedCount to subscribedCount.

components/user/subscriptions/UserPageSubscriptions.tsx (2)

26-26: LGTM!

The import for the new UserPageSubscriptionsEditionPreference component is correctly added.


303-308: LGTM!

The UserPageSubscriptionsEditionPreference component is correctly integrated with the required props (profileKey, details, readonly, refresh) matching its signature from the relevant code snippets.

components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (1)

1-25: Component structure and context usage look solid

The client component wiring (props typing, AuthContext usage, and initial local state) is consistent and clear; no issues with the basic structure or imports.

Comment thread components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (6)
components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (1)

79-125: LGTM! Clean UI implementation with proper accessibility.

The rendering logic is well-structured with:

  • Proper label associations via htmlFor
  • Conditional loading indicator
  • Appropriate disabled states
  • Clear user guidance text

The inline SubscribeAllEditionsBody interface (lines 45-47) could optionally be extracted to a types file or module-level constant for potential reuse, but the current placement is acceptable for a simple, single-use type.

components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (5)

78-80: Eligibility count threading looks good; consider defensive normalization.

Passing subscription_eligibility_count ?? 1 through as eligibilityCount and using it to drive the dropdown makes sense. To be a bit more robust against unexpected backend values (0, negative, NaN) before using it as an array length and in the / N display, consider normalizing inside SubscriptionRow to a minimum positive integer and using that normalized value everywhere.

Also applies to: 116-116


132-141: State sync between subscribed_count and selectedCount is correct; useMemo is optional.

The combination of subscribedCount plus the useEffect that resyncs selectedCount on prop changes correctly keeps the control aligned with refreshed subscription data. Since subscribed_count is already a primitive, you could simplify slightly by computing const subscribedCount = props.subscription.subscribed_count ?? 1; without useMemo, but the current version is functionally fine.


196-204: submit error handling is now sound; you can drop the redundant return in the catch.

Removing the previous setSubscribed(subscribed) no‑op resolves the earlier concern, and finally now guarantees isSubmitting is reset on every path. The explicit return; in the catch block is redundant because there’s no code after the try/catch/finally; you can omit it to tighten the control flow.


207-252: handleUpdateSubscriptionCount is well‑structured; add simple guards and reuse the body type.

The mutation flow here is solid: you guard on isSubmitting/minting_today, re‑check auth, optimistically update selectedCount with a clean revert to subscribedCount on failure, and always clear isSubmitting while surfacing success/error toasts and calling refresh. Two small follow‑ups worth considering:

  • Validate value before posting (e.g., early‑return if it’s < 1 or > props.eligibilityCount) so this stays safe even if the caller or UI changes later.
  • Hoist UpdateSubscriptionCountBody (and SubscribeBody above) to shared type aliases outside the component so they aren’t redefined on every render and can be reused across related subscription calls.

It would also be good to add tests covering both the success and failure paths here to keep this file above the required coverage threshold.


284-327: Action cluster + quantity selector work well; add an explicit label for the <select>.

The right‑hand cluster (spinner, toggle, and conditional quantity selector) respects readonly, isSubmitting, minting_today, and eligibilityCount correctly, and the optimistic update behavior is consistent with the handler. For accessibility, the new <select> currently has no explicit label, which makes its purpose unclear to screen‑reader users; adding an aria-label such as Subscribed quantity for ${props.title} #${props.subscription.token_id} (or wiring it to visible text via aria-labelledby) would address that without changing behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3f40a9 and 20e9031.

📒 Files selected for processing (2)
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Enforce ≥ 80% line coverage for files changed since main via npm run test
All code must pass ESLint (Next's Core Web Vitals + React Hooks rules) via npm run lint
Use <Link> from Next.js for internal navigation instead of <a> tags or HTML anchors
Use <Image> from next/image instead of HTML <img> elements

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: All code must pass TypeScript type checking via npm run type-check (tsc --noEmit)
Use 'use cache' directive at the top of Server Components or functions to explicitly opt-in to caching in Next.js 16

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
🧬 Code graph analysis (2)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (1)
services/api/common-api.ts (1)
  • commonApiPost (230-249)
components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (3)
entities/ISubscription.ts (1)
  • SubscriptionDetails (1-8)
components/auth/Auth.tsx (1)
  • AuthContext (83-93)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (5)

1-11: LGTM! Clean imports and proper client directive.

The "use client" directive is correctly placed for this interactive component, and all imports are appropriately scoped.


13-20: LGTM! Props properly typed with readonly modifier.

The component signature follows coding guidelines with the readonly modifier on props and clear typing.


30-32: LGTM! Proper state synchronization from props.

The effect correctly syncs local state with props changes and has the appropriate dependency array.


34-77: LGTM! Past review comments have been addressed.

The error handling and state management issues from the previous review have been resolved:

  • requestAuth() is now inside the try block, ensuring the finally block executes even if authentication fails or throws
  • ✓ Redundant setIsUpdatingAllEditions(false) removed from catch block
  • ✓ Error message extraction improved with fallback chain: e?.message || String(e) || default

The concurrency guard, authentication flow, API call, and state synchronization are all correctly implemented.


27-28: The default eligibility count of 1 is correct and intentional.

Since details is explicitly optional (SubscriptionDetails | undefined), and subscription_eligibility_count is a required field in the SubscriptionDetails interface, the pattern props.details?.subscription_eligibility_count ?? 1 correctly handles the case when details is undefined (during loading or error states). When data loads, the actual eligibility count from the API response is used. The default of 1 is a safe, reasonable UX fallback and is consistently used across other components in the codebase.

Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableRow.tsx (1)

96-159: Snapshot status cell: avoid initial blank state and fix ARIA on spinner

Two things to tighten up here:

  1. The first render can briefly show a blank status when snapshot.downloaderStatus is unset, because isGeneratingSnapshot defaults to false and is only updated in useEffect. You can derive the initial state from snapshot.downloaderStatus to avoid this flicker:
-  const [isGeneratingSnapshot, setIsGeneratingSnapshot] =
-    useState<boolean>(false);
+  const [isGeneratingSnapshot, setIsGeneratingSnapshot] =
+    useState<boolean>(
+      !snapshot.downloaderStatus ||
+        ![
+          DistributionPlanTokenPoolDownloadStatus.COMPLETED,
+          DistributionPlanTokenPoolDownloadStatus.FAILED,
+        ].includes(snapshot.downloaderStatus)
+    );
  1. The spinner SVG uses aria-hidden="true" together with role="status", which conflicts for assistive tech. Either expose a status or hide the icon. One accessible option is to wrap the SVG and move the status semantics there:
-        {isGeneratingSnapshot ? (
-          <svg
-            aria-hidden="true"
-            role="status"
-            className="tw-w-5 tw-h-5 tw-text-primary-400 tw-animate-spin"
-            viewBox="0 0 100 101"
-            fill="none"
-            xmlns="http://www.w3.org/2000/svg">
+        {isGeneratingSnapshot ? (
+          <span
+            role="status"
+            aria-live="polite"
+            aria-label="Generating snapshot">
+            <svg
+              aria-hidden="true"
+              className="tw-w-5 tw-h-5 tw-text-primary-400 tw-animate-spin"
+              viewBox="0 0 100 101"
+              fill="none"
+              xmlns="http://www.w3.org/2000/svg">
             ...
-          </svg>
+            </svg>
+          </span>

If you want to align even more closely with the guidelines, consider replacing this inline SVG with a FontAwesome spinner icon, but that can be a follow‑up change.

components/user/subscriptions/UserPageSubscriptions.tsx (1)

119-126: Bug: fetchAirdropAddress (function) instead of fetchingAirdropAddress (state) in dependency array.

Line 121 uses fetchAirdropAddress (a function) instead of fetchingAirdropAddress (the loading state). This is likely a typo and causes the effect to have an incorrect dependency.

  }, [
    fetchingDetails,
-   fetchAirdropAddress,
+   fetchingAirdropAddress,
    fetchingTopUpHistory,
    fetchingMemeSubscriptions,
    fetchingSubscriptionLogs,
    fetchingRedeemedHistory,
  ]);
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)

143-145: Potential crash: accessing .toString() on undefined.

If props.plan.name contains no numbers, extractAllNumbers returns an empty array, and [0].toString() throws a TypeError. The nullish coalescing (?? "") won't prevent this.

   const [tokenId, setTokenId] = useState<string>(
-    extractAllNumbers(props.plan.name)[0].toString() ?? ""
+    extractAllNumbers(props.plan.name)[0]?.toString() ?? ""
   );
♻️ Duplicate comments (2)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (1)

37-47: Duplicate function - see earlier comment.

This extractAllNumbers function is duplicated. Please extract to a shared utility as suggested in the upload photos component review.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)

130-140: Duplicate function - see earlier comment.

extractAllNumbers is duplicated here as well. Please use the shared utility.

🧹 Nitpick comments (16)
components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableHeader.tsx (1)

49-51: Empty header cell should have an accessible label for the actions column

The trailing empty <th> is likely for row‑level actions, but being completely empty makes it inaccessible to screen readers. Consider adding a visually hidden label instead of leaving it empty, e.g.:

-      <th
-        scope="col"
-        className="tw-px-3 tw-py-3 tw-whitespace-nowrap tw-text-left tw-text-[0.6875rem] tw-leading-[1.125rem] tw-font-medium tw-text-iron-400 tw-uppercase tw-tracking-[0.25px]"></th>
+      <th
+        scope="col"
+        className="tw-px-3 tw-py-3 tw-whitespace-nowrap tw-text-left tw-text-[0.6875rem] tw-leading-[1.125rem] tw-font-medium tw-text-iron-400 tw-uppercase tw-tracking-[0.25px]">
+        <span className="tw-sr-only">Actions</span>
+      </th>

This keeps the visual design unchanged but improves accessibility.

components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx (1)

4-18: Disabled wiring looks correct; make props type readonly to match guidelines

The disabled={disabled || loading} behavior and visual disabled classes look good and consistent with the other buttons. To align with the “readonly props” guideline for React components, consider updating the inline props type:

-}: {
-  loading: boolean;
-  onClick: () => void;
-  disabled?: boolean;
-}) {
+}: {
+  readonly loading: boolean;
+  readonly onClick: () => void;
+  readonly disabled?: boolean;
+}) {
components/distribution-plan-tool/common/RoundedCsvIconButton.tsx (1)

4-18: Consistent disabled behavior; also mark props readonly

The new disabled prop and disabled={disabled || loading} usage look correct and match the loading semantics. To be consistent with readonly props guidance, you can tighten the props type:

-}: {
-  loading: boolean;
-  onClick: () => void;
-  disabled?: boolean;
-}) {
+}: {
+  readonly loading: boolean;
+  readonly onClick: () => void;
+  readonly disabled?: boolean;
+}) {
components/distribution-plan-tool/common/RoundedJsonIconButton.tsx (1)

4-18: Disabled prop integration is good; align props type with readonly pattern

The disabled handling (disabled={disabled || loading} plus disabled styles) is correct and consistent with the other icon buttons. To match the readonly-props convention, you can adjust the props type as follows:

-}: {
-  loading: boolean;
-  onClick: () => void;
-  disabled?: boolean;
-}) {
+}: {
+  readonly loading: boolean;
+  readonly onClick: () => void;
+  readonly disabled?: boolean;
+}) {
components/user/subscriptions/UserPageSubscriptionsHistory.tsx (1)

85-94: Pagination visibility condition could use page size constant.

The condition props.history.count / 10 > 1 uses a hardcoded 10. Consider extracting this to align with HISTORY_PAGE_SIZE used in the parent component, or pass it as a prop.

-          {props.history.count > 0 && props.history.count / 10 > 1 && (
+          {props.history.count > 0 && props.history.page_size && props.history.count / props.history.page_size > 1 && (

Alternatively, since Page<T> likely doesn't include page_size, the simpler fix is:

+const PAGE_SIZE = 10;
+
 function RedeemedSubscriptionsAccordion(...) {
   ...
-          {props.history.count > 0 && props.history.count / 10 > 1 && (
+          {props.history.count > 0 && props.history.count / PAGE_SIZE > 1 && (

Also applies to: 122-131, 161-169

components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (2)

165-169: Consider moving interface definitions outside async functions.

The SubscribeBody and UpdateSubscriptionCountBody interfaces are defined inside their respective async functions. Moving them to module scope improves readability and allows reuse.

+interface SubscribeBody {
+  contract: string;
+  token_id: number;
+  subscribed: boolean;
+}
+
+interface UpdateSubscriptionCountBody {
+  contract: string;
+  token_id: number;
+  count: number;
+}
+
 function SubscriptionRow(...) {
   ...
   const submit = async (): Promise<void> => {
     ...
-    interface SubscribeBody {
-      contract: string;
-      token_id: number;
-      subscribed: boolean;
-    }
     try {

Also applies to: 212-216


310-314: Optimistic update followed by async call may cause UI flicker on error.

setSelectedCount(value) is called immediately before handleUpdateSubscriptionCount(value), which also sets the value on success. If the API fails, Line 237 reverts to subscribedCount. This works but briefly shows the new value before reverting on error.

Consider making the update purely in the handler or not setting it in onChange.

                    onChange={(e) => {
                      const value = Number.parseInt(e.target.value, 10);
-                     setSelectedCount(value);
                      handleUpdateSubscriptionCount(value);
                    }}
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (2)

368-373: Avoid using querySelector with setTimeout for focus management.

Using document.querySelector('input[placeholder="count"]') is brittle and could break if another input with the same placeholder exists. Consider using a React ref instead.

+import { useRef } from "react";
+
 export default function UserPageSubscriptionsTopUp() {
+  const otherInputRef = useRef<HTMLInputElement>(null);
   ...
            onClick={() => {
              setSelectedOption("other");
              setError("");
-             setTimeout(() => {
-               const input = document.querySelector(
-                 'input[placeholder="count"]'
-               ) as HTMLInputElement;
-               input?.focus();
-             }, 0);
+             setTimeout(() => otherInputRef.current?.focus(), 0);
            }}>
   ...
                <Form.Control
+                 ref={otherInputRef}
                  type="number"
                  min={1}
                  placeholder="count"

Also applies to: 383-388


416-428: Redundant try/catch branches—all paths execute the same code.

The try/catch/else branches all set memeCount(value) and setSelectedOption("other"). This can be simplified.

                  onChange={(e) => {
                    setError("");
                    const value = e.target.value;
-                   try {
-                     const parsed = parseInt(value);
-                     if (!isNaN(parsed) && parsed > 0) {
-                       setMemeCount(value);
-                       setSelectedOption("other");
-                     } else {
-                       setMemeCount(value);
-                       setSelectedOption("other");
-                     }
-                   } catch {
-                     setMemeCount(value);
-                     setSelectedOption("other");
-                   }
+                   setMemeCount(value);
+                   setSelectedOption("other");
                  }}

The validation is already handled in handleSend(), so there's no need for additional parsing logic here.

hooks/useManifoldClaim.ts (1)

210-221: Consider handling errors before initial claim data loads.

The error handler only sets isError: true if prevClaim exists. If readContract.error occurs before any successful data fetch (i.e., prevClaim is undefined), the error state won't be stored. Depending on your UX intent, you may want to initialize a partial claim with isError: true so the UI can display an error message instead of an indefinite loading state.

Example approach:

 useEffect(() => {
   if (readContract.error) {
     setClaim((prevClaim) => {
       if (prevClaim) {
         return {
           ...prevClaim,
           isError: true,
         };
+      } else {
+        return {
+          instanceId: 0,
+          total: 0,
+          totalMax: 0,
+          remaining: 0,
+          cost: 0,
+          startDate: 0,
+          endDate: 0,
+          status: ManifoldClaimStatus.UPCOMING,
+          phase: ManifoldPhase.PUBLIC,
+          isFetching: false,
+          isFinalized: false,
+          isSoldOut: false,
+          isError: true,
+        };
       }
     });
     onError?.();
   }
 }, [readContract.error, onError]);
components/mint-countdown-box/MemePageMintCountdown.tsx (1)

20-22: Memoize the error callback to avoid unnecessary effect executions.

The inline arrow function is recreated on every render, causing the useEffect in useManifoldClaim (which depends on onError) to re-run unnecessarily.

Apply this diff:

+import { useCallback, useEffect, useState } from "react";
+
 export default function MemePageMintCountdown(
   props: Readonly<{
     nft_id: number;
     hide_mint_btn?: boolean;
   }>
 ) {
   const [isError, setIsError] = useState(false);
-  const manifoldClaim = useMemesManifoldClaim(props.nft_id, () => {
-    setIsError(true);
-  });
+  const handleError = useCallback(() => {
+    setIsError(true);
+  }, []);
+  const manifoldClaim = useMemesManifoldClaim(props.nft_id, handleError);
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (1)

91-103: Remove redundant type assertion.

null as null is unnecessary since null already has the null type:

       const publicRow: ReviewDistributionPlanTablePhase = {
         phase: {
           id: PUBLIC_SUBSCRIPTIONS_PHASE_ID,
           phaseId: PUBLIC_SUBSCRIPTIONS_PHASE_ID,
-          componentId: null as null,
+          componentId: null,
           type: ReviewDistributionPlanTableItemType.PHASE,
           name: "Public",
           description: "Auto-generated",
           walletsCount: 0,
           spotsCount: 0,
         },
         components: [],
       };
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

9-19: Duplicate utility function across files.

extractAllNumbers is duplicated in three files: this file, ReviewDistributionPlanTableSubscriptionFooter.tsx, and ReviewDistributionPlanTableSubscription.tsx. Extract this to a shared utility module.

Create a shared utility, e.g., in helpers/Helpers.ts:

export function extractAllNumbers(str: string): number[] {
  const regex = /\d+/g;
  const numbers = [];
  let match;
  while ((match = regex.exec(str)) !== null) {
    numbers.push(parseInt(match[0]));
  }
  return numbers;
}
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (2)

92-99: Extract duplicate overview refetch logic.

The same fetch-and-set pattern for DistributionOverview appears after reset (line 92-99), upload (line 210-213), and finalize (line 272-275).

Extract to a reusable function:

const refetchOverview = async (contract: string, tokenId: string) => {
  try {
    const data = await commonApiFetch<DistributionOverview>({
      endpoint: `distributions/${contract}/${tokenId}/overview`,
    });
    setOverview(data);
  } catch (error) {
    console.error("Failed to fetch distribution overview:", error);
  }
};

Also applies to: 210-213, 272-275


183-232: Consider extracting inline handler.

The onUpload handler is ~50 lines. Extracting to a named function would improve readability:

const handlePhotoUpload = async (contract: string, tokenId: string, files: File[]) => {
  setShowUploadPhotos(false);
  setIsUploading(true);
  // ... rest of logic
};
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)

51-82: Consolidate duplicate button blocks.

The public and non-public buttons are nearly identical, differing only in the label text. Consider consolidating:

-      {!isPublic && (
-        <button
-          onClick={() => setShowConfirm(true)}
-          disabled={downloading}
-          type="button"
-          className="tw-px-3 tw-group tw-rounded-full tw-group tw-flex tw-items-center tw-justify-center tw-h-8 tw-text-xs tw-font-medium tw-border-none tw-ring-1 tw-ring-inset tw-text-white tw-bg-blue-500 tw-ring-iron-500/50 hover:tw-bg-blue-500/50 tw-ease-out tw-transition tw-duration-300">
-          {downloading ? (
-            <span className="d-flex gap-2 align-items-center">
-              <CircleLoader />
-              <span>Downloading</span>
-            </span>
-          ) : (
-            <>Subscription Lists</>
-          )}
-        </button>
-      )}
-      {isPublic && (
-        <button
-          onClick={() => setShowConfirm(true)}
-          disabled={downloading}
-          type="button"
-          className="tw-px-3 tw-group tw-rounded-full tw-group tw-flex tw-items-center tw-justify-center tw-h-8 tw-text-xs tw-font-medium tw-border-none tw-ring-1 tw-ring-inset tw-text-white tw-bg-blue-500 tw-ring-iron-500/50 hover:tw-bg-blue-500/50 tw-ease-out tw-transition tw-duration-300">
-          {downloading ? (
-            <span className="d-flex gap-2 align-items-center">
-              <CircleLoader />
-              <span>Downloading</span>
-            </span>
-          ) : (
-            <>Public Subscriptions</>
-          )}
-        </button>
-      )}
+      <button
+        onClick={() => setShowConfirm(true)}
+        disabled={downloading}
+        type="button"
+        className="tw-px-3 tw-group tw-rounded-full tw-group tw-flex tw-items-center tw-justify-center tw-h-8 tw-text-xs tw-font-medium tw-border-none tw-ring-1 tw-ring-inset tw-text-white tw-bg-blue-500 tw-ring-iron-500/50 hover:tw-bg-blue-500/50 tw-ease-out tw-transition tw-duration-300">
+        {downloading ? (
+          <span className="d-flex gap-2 align-items-center">
+            <CircleLoader />
+            <span>Downloading</span>
+          </span>
+        ) : (
+          <>{isPublic ? "Public Subscriptions" : "Subscription Lists"}</>
+        )}
+      </button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20e9031 and 1937477.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • components/distribution-plan-tool/common/RoundedCsvIconButton.tsx (1 hunks)
  • components/distribution-plan-tool/common/RoundedJsonIconButton.tsx (1 hunks)
  • components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx (1 hunks)
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableHeader.tsx (1 hunks)
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableRow.tsx (2 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (2 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableRow.tsx (2 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (6 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (4 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/constants.ts (1 hunks)
  • components/distribution/Distribution.tsx (9 hunks)
  • components/manifoldMinting/ManifoldMinting.tsx (2 hunks)
  • components/mint-countdown-box/MemePageMintCountdown.tsx (3 hunks)
  • components/mint-countdown-box/MintCountdownBox.tsx (2 hunks)
  • components/user/subscriptions/UserPageSubscriptions.module.scss (2 hunks)
  • components/user/subscriptions/UserPageSubscriptions.tsx (4 hunks)
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx (4 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (8 hunks)
  • entities/IDistribution.ts (1 hunks)
  • hooks/useManifoldClaim.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Enforce ≥ 80% line coverage for files changed since main via npm run test
All code must pass ESLint (Next's Core Web Vitals + React Hooks rules) via npm run lint
Use <Link> from Next.js for internal navigation instead of <a> tags or HTML anchors
Use <Image> from next/image instead of HTML <img> elements

Files:

  • components/mint-countdown-box/MemePageMintCountdown.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/constants.ts
  • components/distribution-plan-tool/common/RoundedCsvIconButton.tsx
  • hooks/useManifoldClaim.ts
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableRow.tsx
  • components/manifoldMinting/ManifoldMinting.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableRow.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/distribution-plan-tool/common/RoundedJsonIconButton.tsx
  • entities/IDistribution.ts
  • components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableHeader.tsx
  • components/user/subscriptions/UserPageSubscriptions.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • components/distribution/Distribution.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/mint-countdown-box/MemePageMintCountdown.tsx
  • components/distribution-plan-tool/common/RoundedCsvIconButton.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableRow.tsx
  • components/manifoldMinting/ManifoldMinting.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableRow.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/distribution-plan-tool/common/RoundedJsonIconButton.tsx
  • components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableHeader.tsx
  • components/user/subscriptions/UserPageSubscriptions.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • components/distribution/Distribution.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: All code must pass TypeScript type checking via npm run type-check (tsc --noEmit)
Use 'use cache' directive at the top of Server Components or functions to explicitly opt-in to caching in Next.js 16

Files:

  • components/mint-countdown-box/MemePageMintCountdown.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/constants.ts
  • components/distribution-plan-tool/common/RoundedCsvIconButton.tsx
  • hooks/useManifoldClaim.ts
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableRow.tsx
  • components/manifoldMinting/ManifoldMinting.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableRow.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/distribution-plan-tool/common/RoundedJsonIconButton.tsx
  • entities/IDistribution.ts
  • components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx
  • components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableHeader.tsx
  • components/user/subscriptions/UserPageSubscriptions.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • components/distribution/Distribution.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Use TypeScript and React functional components with hooks

Applied to files:

  • components/mint-countdown-box/MemePageMintCountdown.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:35:58.721Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.721Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Fix issues with modernization aligned to React 19.2, React Compiler, and Next.js 16 conventions; do not add `// eslint-disable` comments unless explicitly instructed

Applied to files:

  • components/distribution/Distribution.tsx
🧬 Code graph analysis (7)
components/mint-countdown-box/MemePageMintCountdown.tsx (1)
hooks/useManifoldClaim.ts (1)
  • useMemesManifoldClaim (237-245)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableRow.tsx (5)
components/distribution-plan-tool/review-distribution-plan/table/constants.ts (1)
  • PUBLIC_SUBSCRIPTIONS_PHASE_ID (1-1)
components/distribution-plan-tool/common/DistributionPlanTableRowWrapper.tsx (1)
  • DistributionPlanTableRowWrapper (3-17)
components/distribution-plan-tool/common/RoundedJsonIconButton.tsx (1)
  • RoundedJsonIconButton (4-24)
components/distribution-plan-tool/common/RoundedCsvIconButton.tsx (1)
  • RoundedCsvIconButton (4-24)
components/distribution-plan-tool/common/RoundedManifoldIconButton.tsx (1)
  • RoundedManifoldIconButton (4-24)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (3)
components/auth/Auth.tsx (1)
  • AuthContext (83-93)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)
  • isSubscriptionsAdmin (218-224)
components/distribution-plan-tool/review-distribution-plan/table/constants.ts (1)
  • PUBLIC_SUBSCRIPTIONS_PHASE_ID (1-1)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (3)
components/allowlist-tool/allowlist-tool.types.ts (1)
  • AllowlistDescription (12-18)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
helpers/Helpers.ts (1)
  • formatAddress (31-46)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (3)
components/show-more-button/ShowMoreButton.tsx (1)
  • ShowMoreButton (7-35)
services/api/common-api.ts (1)
  • commonApiPost (230-249)
components/meme-calendar/meme-calendar.helpers.tsx (1)
  • formatFullDate (669-677)
components/distribution/Distribution.tsx (5)
entities/IDistribution.ts (1)
  • Distribution (8-21)
helpers/Helpers.ts (2)
  • numberWithCommas (83-100)
  • areEqualAddresses (149-154)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
components/the-memes/UpcomingMemePage.tsx (1)
  • UpcomingMemePage (4-14)
components/not-found/NotFound.tsx (1)
  • NotFound (7-51)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (2)
components/distribution-plan-tool/review-distribution-plan/table/constants.ts (1)
  • PUBLIC_SUBSCRIPTIONS_PHASE_ID (1-1)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (32)
components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableHeader.tsx (1)

6-24: New Status / Contract number headers look consistent; verify body columns are updated

The added “Status” and “Contract number” headers match the existing typography and spacing, so structurally this looks good. Please double‑check that the corresponding <td> cells in the snapshot rows were updated to include these new columns in the same order to avoid misaligned data and header/body mismatch.

components/distribution-plan-tool/create-snapshots/table/CreateSnapshotTableRow.tsx (1)

221-225: Tooltip style adjustment looks good

The Tooltip style object and JSX closure are syntactically correct and keep the existing behavior; no changes needed here.

components/manifoldMinting/ManifoldMinting.tsx (2)

148-148: LGTM: Clean formatting change.

The heading formatting is clear and follows standard JSX conventions.


514-520: Type safety verified — airdrops property exists and is properly typed.

The airdrops property is defined as number in the Distribution interface (entities/IDistribution.ts:15), confirming type safety. The optional chaining distribution?.airdrops correctly handles undefined distribution instances. The conditional rendering pattern is sound: the UI displays only when airdrops > 0, which is intentional.

components/user/subscriptions/UserPageSubscriptions.module.scss (3)

27-51: Table styling with alternating rows looks good.

The alternating row backgrounds (#242428 for even, #1c1c21 for odd) provide good visual distinction. The !important usage is acceptable here for overriding Bootstrap defaults.


83-110: Button styling is well-structured with proper state handling.

Good use of :not(:disabled) selectors for hover/active states, and the disabled state properly conveys the non-interactive state with reduced opacity and cursor: not-allowed.


112-122: Radio button styling uses global selectors correctly.

The :global() wrapper is necessary here since Bootstrap's Form.Check generates class names that would otherwise be scoped.

components/user/subscriptions/UserPageSubscriptionsHistory.tsx (2)

21-59: Component structure with readonly props follows guidelines.

The main component properly destructures paginated data and provides setters for page navigation. The structure is clean with proper separation of concerns.


240-280: RedeemedEntry handles optional transaction_date gracefully.

The fallback to created_at when transaction_date is undefined ensures dates are always displayed.

components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (3)

125-134: State synchronization pattern is correct.

The useMemo for subscribedCount and useEffect to sync selectedCount properly handles external updates to the subscription data.


200-246: handleUpdateSubscriptionCount properly handles errors and resets state.

Good error handling with state reversion on failure. The finally block ensures isSubmitting is always reset.


298-330: Quantity selector UI implementation is well-structured.

The dropdown is properly disabled based on eligibilityCount, readonly, isSubmitting, and minting_today states. The conditional rendering when subscribed is correct.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (4)

142-151: Body scroll lock with proper cleanup.

The effect correctly locks scroll when modal is shown and cleans up on unmount. Good pattern for modal overlays.


153-176: Escape key handler with proper dependency management.

The closeModal callback is properly memoized with useCallback, and the escape handler is correctly added/removed based on modal state.


491-543: Modal portal implementation is correct.

The mounted check before createPortal prevents SSR hydration mismatches. The AnimatePresence wrapper enables smooth enter/exit animations.


548-584: CardCountOption component is clean and reusable.

The component properly uses readonly props and handles selection state. Good separation of concerns.

components/user/subscriptions/UserPageSubscriptions.tsx (3)

263-267: Good pattern: Reusable column configuration object.

The colConfig object provides a single source of truth for consistent grid layout across all subscription panels. This reduces duplication and makes layout changes easier.


287-318: Grid layout with new EditionPreference component is well-structured.

The four columns (Balance, AirdropAddress, Mode, EditionPreference) use consistent sizing via colConfig. The responsive breakpoints (xs=12, md=6) provide good mobile/desktop layouts.


322-328: Conditional TopUp rendering for connected accounts is correct.

The TopUp section is appropriately gated behind isConnectedAccount check, preventing unauthorized users from seeing top-up controls.

components/distribution-plan-tool/review-distribution-plan/table/constants.ts (1)

1-1: LGTM!

Centralizing this magic string as a constant is the right approach for maintainability and consistency across the table components.

entities/IDistribution.ts (1)

4-5: Verify API contract for new fields.

These fields are typed as required (number), but in Distribution.tsx (lines 157-158), the code uses optional chaining (p?.spots_airdrop ?? 0), suggesting they might be optional. If the API can return null or omit these fields, consider making them optional:

 interface DistributionPhaseEntry {
   phase: string;
   spots: number;
-  spots_airdrop: number;
-  spots_allowlist: number;
+  spots_airdrop?: number;
+  spots_allowlist?: number;
 }
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableRow.tsx (1)

192-214: LGTM!

The isPublic flag correctly guards the display of counts (showing "-" instead of 0) and disables action buttons to prevent invalid API calls for the synthetic public phase row. This aligns well with the button components that already support the disabled prop.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (1)

50-108: Well-structured refactor of rows construction.

The use of reduce to accumulate both components and spotsCount in a single pass is efficient. The conditional addition of the public phase row for admins is cleanly implemented with proper dependency tracking. Based on learnings, this follows React functional components with hooks pattern correctly.

components/distribution/Distribution.tsx (2)

43-44: LGTM!

Deriving nftNan and nftId directly from params is cleaner than the previous useEffect synchronization approach. This reduces state management complexity and potential stale state issues.


309-311: Good early return for invalid route.

Returning NotFound early when the route param is not a valid number is the correct approach—it prevents unnecessary API calls and provides immediate user feedback.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (3)

30-32: Token ID won't update if plan prop changes.

useState initializer runs only once on mount. If this modal is reused for different plans without unmounting, tokenId will show stale data from the first plan.

Verify the modal is unmounted between uses, or add a useEffect to sync state:

useEffect(() => {
  setTokenId(extractAllNumbers(props.plan.name)[0]?.toString() ?? "");
}, [props.plan.name]);

48-68: LGTM!

File validation logic correctly handles empty file lists, invalid MIME types, and size limits with clear error messages.


121-228: LGTM!

Modal structure is well-organized with proper state reset on close, disabled button guards, and clear user feedback for validation errors and selected files.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (2)

49-73: LGTM with minor observation.

The overview fetch logic is well-structured with proper guards. Note that connectedProfile object reference changes could trigger refetches, but the admin check guards against unnecessary API calls.


114-175: LGTM!

Button layout with loading states, disabled guards, and status indicators (photo count, normalization checkmark) provides good user feedback.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (2)

179-188: LGTM!

The normalization warning provides clear feedback to admins about potential data overwrite, with appropriate styling.


206-293: LGTM!

CSV download utilities (mergeResults, convertToCSV, downloadCSV, processResults) are well-structured and handle the different output types correctly for public vs non-public phases.

Comment thread components/mint-countdown-box/MemePageMintCountdown.tsx
Comment thread components/mint-countdown-box/MintCountdownBox.tsx
Comment thread components/user/subscriptions/UserPageSubscriptionsTopUp.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

257-265: Use a standard anchor tag for external navigation with target="_blank".

Using Next.js Link with window.location.href as the href is an anti-pattern. The Link component is designed for client-side routing within the app, not for opening the current URL in a new tab.

Apply this diff to use a standard anchor tag instead:

-        <Link
-          href={window.location.href}
-          className="text-center pt-2 pb-2"
-          target="_blank"
-          rel="noopener noreferrer">
+        <a
+          href={typeof window !== 'undefined' ? window.location.href : ''}
+          className="text-center pt-2 pb-2"
+          target="_blank"
+          rel="noopener noreferrer">
           <button className="btn btn-light" style={{ width: "100%" }}>
             Top-up on 6529.io
           </button>
-        </Link>
+        </a>
♻️ Duplicate comments (3)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

146-146: Past review comment still applies: parseInt on empty string returns NaN, causing input display issues.

When the user clears the input field, tokenId becomes "", and parseInt("") returns NaN, which breaks the controlled input display.

Apply this diff to fix the issue:

-              value={Number.parseInt(tokenId, 10)}
+              value={tokenId === "" ? "" : Number.parseInt(tokenId, 10)}

Alternatively, store tokenId as a number or use a more robust approach:

-              value={Number.parseInt(tokenId, 10)}
+              value={tokenId}

Then ensure validation elsewhere handles non-numeric strings appropriately (which lines 100-101 already do).

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

507-508: Missing space between card count and "Cards".

This issue was previously identified. There's no space between the card count and the word "Cards", resulting in display like "5Cards" instead of "5 Cards".

Apply this diff to fix:

                       <p className="tw-text-iron-400 tw-text-sm tw-mt-1">
-                        {(topUpAmount / MEMES_MINT_PRICE).toLocaleString()}
-                        Cards -{" "}
+                        {(topUpAmount / MEMES_MINT_PRICE).toLocaleString()}{" "}
+                        Cards -{" "}
                         {numberWithCommasFromString(
                           topUpAmount.toString()
                         )}{" "}
components/distribution/Distribution.tsx (1)

150-172: Normalize getSpotsForPhase return type (string/JSX/undefined).

This helper currently returns "", a JSX element, or undefined (implicit fall-through when count <= 0), which complicates typing and makes the calling <td> harder to reason about. Aligning on JSX.Element | null (or React.ReactNode) would be cleaner and match React conventions. This re-iterates the earlier feedback on this function.

You can make the return type explicit and use null for “no content” paths:

-  function getSpotsForPhase(d: Distribution, phase: string) {
-    if (phase.toUpperCase() === "AIRDROP") {
-      return "";
-    }
-    const p = d.allowlist.find((a) => a.phase === phase);
-    const count = p?.spots ?? 0;
-    if (count > 0) {
-      const spotsAirdrop = p?.spots_airdrop ?? 0;
-      const spotsAllowlist = p?.spots_allowlist ?? 0;
-
-      if (!spotsAirdrop && !spotsAllowlist) {
-        return "";
-      }
-
-      return (
-        <span className="tw-text-iron-400 tw-text-sm">
-          {spotsAirdrop > 0 ? numberWithCommas(spotsAirdrop) : "0"}
-          {" | "}
-          {spotsAllowlist > 0 ? numberWithCommas(spotsAllowlist) : "0"}
-        </span>
-      );
-    }
-  }
+  function getSpotsForPhase(
+    d: Distribution,
+    phase: string
+  ): JSX.Element | null {
+    if (phase.toUpperCase() === "AIRDROP") {
+      return null;
+    }
+
+    const p = d.allowlist.find((a) => a.phase === phase);
+    const count = p?.spots ?? 0;
+    if (!p || count <= 0) {
+      return null;
+    }
+
+    const spotsAirdrop = p.spots_airdrop ?? 0;
+    const spotsAllowlist = p.spots_allowlist ?? 0;
+
+    if (!spotsAirdrop && !spotsAllowlist) {
+      return null;
+    }
+
+    return (
+      <span className="tw-text-iron-400 tw-text-sm">
+        {spotsAirdrop > 0 ? numberWithCommas(spotsAirdrop) : "0"}
+        {" | "}
+        {spotsAllowlist > 0 ? numberWithCommas(spotsAllowlist) : "0"}
+      </span>
+    );
+  }

This keeps the caller logic unchanged while making the function’s contract much clearer.

🧹 Nitpick comments (6)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

88-96: Consider extracting optionCountMap outside the function.

The optionCountMap is recreated on every handleSend invocation. For better performance, consider defining it outside the function or using useMemo if it depends on reactive values.

Apply this diff to extract the constant:

+const OPTION_COUNT_MAP_KEYS = {
+  ONE: "1",
+  SZN: "szn",
+  YEAR: "year",
+  EPOCH: "epoch",
+  PERIOD: "period",
+  ERA: "era",
+  EON: "eon",
+} as const;
+
 export default function UserPageSubscriptionsTopUp() {
   const { isIos } = useCapacitor();
   // ... other state ...
   
   function handleSend() {
     setError("");
     let count: number;
     if (selectedOption && selectedOption !== "other") {
-      const optionCountMap: Record<string, number> = {
-        "1": 1,
-        szn: remainingMintsForSeason,
-        year: remainingMintsForYear,
-        epoch: remainingMintsForEpoch,
-        period: remainingMintsForPeriod,
-        era: remainingMintsForEra,
-        eon: remainingMintsForEon,
-      };
+      const optionCountMap: Record<string, number> = {
+        [OPTION_COUNT_MAP_KEYS.ONE]: 1,
+        [OPTION_COUNT_MAP_KEYS.SZN]: remainingMintsForSeason,
+        [OPTION_COUNT_MAP_KEYS.YEAR]: remainingMintsForYear,
+        [OPTION_COUNT_MAP_KEYS.EPOCH]: remainingMintsForEpoch,
+        [OPTION_COUNT_MAP_KEYS.PERIOD]: remainingMintsForPeriod,
+        [OPTION_COUNT_MAP_KEYS.ERA]: remainingMintsForEra,
+        [OPTION_COUNT_MAP_KEYS.EON]: remainingMintsForEon,
+      };
       count = optionCountMap[selectedOption];
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (3)

40-115: Admin gating + public/non‑public buttons look consistent; consider deduplicating button markup

The admin/PHASE guard and isPublic flag derived from PUBLIC_SUBSCRIPTIONS_PHASE_ID are coherent and correctly prevent non‑admins or non‑PHASE rows from seeing download actions. The two button branches, however, duplicate the same structure with only the label text differing; extracting a small helper or passing the label as a variable would reduce repetition and keep future styling tweaks in one place.


164-177: Keep tokenId as a string in the input value to avoid NaN edge cases

You currently store tokenId as a string but render the input with value={Number.parseInt(tokenId, 10)}. When tokenId is empty or invalid this can produce NaN, which React doesn’t handle as cleanly for controlled inputs as a plain string.

Since you already validate with Number.parseInt in the button’s disabled condition, you can keep the input purely string‑based:

-              Token ID:{" "}
-              <input
-                style={{
-                  color: "black",
-                  width: "100px",
-                }}
-                min={1}
-                type="number"
-                value={Number.parseInt(tokenId, 10)}
-                onChange={(e) => {
-                  setTokenId(e.target.value);
-                }}
-              />
+              Token ID:{" "}
+              <input
+                style={{
+                  color: "black",
+                  width: "100px",
+                }}
+                min={1}
+                type="number"
+                value={tokenId}
+                onChange={(e) => {
+                  setTokenId(e.target.value);
+                }}
+              />
@@
-        <Button
-          disabled={!tokenId || Number.isNaN(Number.parseInt(tokenId, 10))}
+        <Button
+          disabled={!tokenId || Number.isNaN(Number.parseInt(tokenId, 10))}
           variant="primary"
           onClick={() => props.onConfirm(contract, tokenId)}>

This keeps the controlled input simple while preserving your current validation.

Also applies to: 196-200


226-249: Align public handling in processResults with PUBLIC_SUBSCRIPTIONS_PHASE_ID

download distinguishes phases via phaseId, but processResults branches on phaseName === "public". Now that you introduced PUBLIC_SUBSCRIPTIONS_PHASE_ID and are using it elsewhere (e.g., isPublic in SubscriptionLinks), relying on the literal "public" here is a bit brittle and can drift if display names change.

Consider threading phaseId (or a boolean isPublic) into processResults so that the CSV behavior is driven by the same source of truth as the UI:

-export const download = async (
-  contract: string,
-  tokenId: string,
-  planId: string,
-  phaseId: string,
-  phaseName: string
-) => {
+export const download = async (
+  contract: string,
+  tokenId: string,
+  planId: string,
+  phaseId: string,
+  phaseName: string
+) => {
@@
-    .then((response) => {
-      processResults(phaseName, response);
+    .then((response) => {
+      processResults(phaseId, phaseName, response);
@@
-const processResults = (phaseName: string, results: SubscriptionResult) => {
-  if (phaseName === "public") {
+const processResults = (
+  phaseId: string,
+  phaseName: string,
+  results: SubscriptionResult
+) => {
+  if (phaseId === PUBLIC_SUBSCRIPTIONS_PHASE_ID) {

This keeps “publicness” keyed off the same constant instead of a magic string while preserving filenames via phaseName.

Also applies to: 251-264

components/distribution/Distribution.tsx (2)

43-44: Tighten nftId validation and re-check hooks behavior.

Number.parseInt will treat values like "123abc" as valid IDs. If the route param is intended to be strictly numeric, consider validating the raw string instead of relying on parseInt:

-  const isValidNftId = !Number.isNaN(Number.parseInt(params?.id as string, 10));
-  const nftId = isValidNftId ? (params?.id as string) : "";
+  const rawId = params?.id as string | undefined;
+  const isValidNftId = typeof rawId === "string" && /^\d+$/.test(rawId);
+  const nftId = isValidNftId ? rawId : "";

Also, since nftId now gates all fetching logic, it’s worth re-running the React Hooks lint to ensure the effects that depend on nftId / pageProps don’t get flagged for missing dependencies; a small tweak like using a functional setPageProps update in the search reset effect can help keep dependencies minimal without adding // eslint-disable comments. Based on learnings, prefer adjusting dependencies over disabling ESLint.


237-241: Consider moving flex layout into an inner wrapper instead of <td>.

Using tw-flex directly on <td> changes the cell’s display to flex, which can sometimes interact oddly with table layouts and responsive behavior. Consider keeping the <td> using the table display model and wrapping the contents in a flex container:

-                        <td
-                          key={`${p}-${d.wallet}`}
-                          className="tw-flex tw-justify-center tw-items-center">
-                          {getCountForPhase(d, p)}&nbsp;&nbsp;
-                          {getSpotsForPhase(d, p)}
-                        </td>
+                        <td key={`${p}-${d.wallet}`}>
+                          <div className="tw-flex tw-justify-center tw-items-center">
+                            {getCountForPhase(d, p)}&nbsp;&nbsp;
+                            {getSpotsForPhase(d, p)}
+                          </div>
+                        </td>

Not mandatory, but it keeps table semantics a bit cleaner.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1937477 and b4a87c3.

📒 Files selected for processing (6)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (6 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (4 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1 hunks)
  • components/distribution/Distribution.tsx (9 hunks)
  • components/user/subscriptions/UserPageSubscriptions.module.scss (2 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/user/subscriptions/UserPageSubscriptions.module.scss
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Enforce ≥ 80% line coverage for files changed since main via npm run test
All code must pass ESLint (Next's Core Web Vitals + React Hooks rules) via npm run lint
Use <Link> from Next.js for internal navigation instead of <a> tags or HTML anchors
Use <Image> from next/image instead of HTML <img> elements

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
  • components/distribution/Distribution.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
  • components/distribution/Distribution.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: All code must pass TypeScript type checking via npm run type-check (tsc --noEmit)
Use 'use cache' directive at the top of Server Components or functions to explicitly opt-in to caching in Next.js 16

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
  • components/distribution/Distribution.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T08:35:58.721Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.721Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Use TypeScript and React functional components with hooks

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Fix issues with modernization aligned to React 19.2, React Compiler, and Next.js 16 conventions; do not add `// eslint-disable` comments unless explicitly instructed

Applied to files:

  • components/distribution/Distribution.tsx
🧬 Code graph analysis (4)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (3)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)
  • isSubscriptionsAdmin (218-224)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)
  • UploadDistributionPhotosModal (21-231)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (3)
components/allowlist-tool/allowlist-tool.types.ts (1)
  • AllowlistDescription (12-18)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
helpers/Helpers.ts (1)
  • formatAddress (31-46)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)
components/distribution-plan-tool/review-distribution-plan/table/constants.ts (1)
  • PUBLIC_SUBSCRIPTIONS_PHASE_ID (1-1)
components/distribution/Distribution.tsx (4)
entities/IDistribution.ts (1)
  • Distribution (8-21)
helpers/Helpers.ts (2)
  • numberWithCommas (83-100)
  • areEqualAddresses (149-154)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
components/the-memes/UpcomingMemePage.tsx (1)
  • UpcomingMemePage (4-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (12)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (4)

49-73: LGTM: Distribution overview fetch is properly implemented.

The useEffect correctly fetches the distribution overview on component mount and when dependencies change. Error handling is appropriate, and the early returns prevent unnecessary API calls.


115-175: LGTM: UI layout properly reflects the new workflow.

The vertical stacking with three distinct button groups (Reset, Upload, Finalize) clearly separates the actions. The photo count indicator and normalization status indicator provide helpful visual feedback to admins.


249-294: <!-- [scratchpad_start]
[task review_verification 1/1]
[observations]

  • This is a frontend repository (6529seize-frontend)
  • Backend API endpoint is configured via publicEnv.API_ENDPOINT
  • commonApiPost builds URLs as /api/${endpoint}
  • normalize endpoint will be: /api/distributions/${contract}/${tokenId}/normalize
  • The backend API is external and not included in this frontend codebase
  • Cannot verify backend implementation from frontend codebase

[analysis]
Frontend code verification:

  • Response type is correctly specified: { success: boolean; message?: string; error?: string }
  • Response is parsed via JSON.parse (via res.json())
  • Code safely accesses response.success before proceeding
  • Error handling is in place with try-catch
  • After success, code fetches overview endpoint and expects DistributionOverview

Code is correct on frontend side:

  • Proper TypeScript typing for response
  • Safe response handling with checks
  • Proper error handling

What cannot be verified:

  • Whether backend endpoint actually exists
  • Whether backend returns the exact response shape
  • Whether backend implementation matches frontend expectations

[conclusion]
The review comment asks to verify backend endpoint signature, but:

  1. This is a frontend-only codebase
  2. Backend implementation is external
  3. Frontend code is correctly typed and handles responses safely
  4. The verification request is reasonable but scope is outside frontend codebase
  5. The code appears to be implementing the contract correctly based on what can be observed

This is not an error in the code - it's a reasonable verification request that cannot be fully satisfied without backend access.

[done]
Verification complete. Frontend code is correct. Backend verification not possible from frontend codebase.
</scratchpad_end] -->

Based on my verification, I can now provide the rewritten review comment:

Normalize endpoint appears correctly implemented in frontend; backend verification not possible from frontend codebase.

The frontend code at lines 249-294 correctly implements the endpoint call with proper TypeScript typing. The response shape { success: boolean; message?: string; error?: string } is properly typed for commonApiPost, and the response handling safely checks response.success before proceeding. The subsequent overview fetch expects DistributionOverview with is_normalized and photos_count fields, which aligns with the code at line 248.

However, verification of the backend /api/distributions/${contract}/${tokenId}/normalize endpoint signature cannot be completed as this is a frontend-only repository and the backend API is external. This verification request should be directed to the backend codebase or API documentation.


183-232: The photo upload API endpoint signature and multipart form handling are implemented correctly.

The implementation properly:

  • Uses standard multipart FormData with the "photos" key for multiple file uploads
  • Calls commonApiPostForm, which correctly omits the Content-Type header (passing false to getHeaders), allowing the browser to automatically set Content-Type: multipart/form-data with the correct boundary
  • Includes necessary authentication headers via getHeaders
  • Sends FormData directly without modification to executeApiRequest
  • Declares the expected response type as { success: boolean; photos?: string[]; error?: string }
  • Handles all error cases (success, error message, exception)

The endpoint distribution_photos/${contract}/${tokenId} is used consistently for both GET (fetching DistributionPhoto[]) and POST (uploading photos) operations, which is verified across the codebase.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

541-582: LGTM! Well-structured option component.

The CardCountOption component follows React best practices:

  • Semantic button element with proper type
  • Readonly props as per coding guidelines
  • Clean conditional rendering and styling
  • Proper accessibility with radio inputs

The component is well-designed for the selection UI pattern used throughout the file.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)

179-188: isNormalized warning block is clear and low‑risk

The new isNormalized‑gated warning is straightforward and clearly communicates that the operation will overwrite existing normalized data. The conditional render is safe (no side effects) and defaults to not rendering when the prop is omitted, which keeps existing call sites behaviorally unchanged.

components/distribution/Distribution.tsx (6)

6-14: New NotFound/UpcomingMemePage imports are correctly wired.

Both imports are used in this file and the paths match the referenced components; no changes needed here.


134-135: Explicit empty fragment return from printDistributionPhotos looks good.

Returning <></> when there are no photos keeps the return type consistently JSX and matches the always-rendered photos row below.


263-267: Mint countdown gating is safer and more precise now.

Conditioning MemePageMintCountdown on areEqualAddresses(props.contract, MEMES_CONTRACT) plus isValidNftId && nftId avoids rendering the countdown for non-MEMES contracts or invalid IDs and ensures the parsed nft_id is numeric before use. This looks correct.


276-278: Empty state integration with UpcomingMemePage looks good.

Reusing UpcomingMemePage in the empty state is a nice touch and, combined with the isValidNftId guard above, ensures it only runs for numeric IDs.


313-315: Early NotFound for invalid id is a solid UX improvement.

Short-circuiting with <NotFound label="DISTRIBUTION" /> when the URL id is invalid prevents unnecessary fetches and gives a clear 404-style experience for bad routes.


322-333: Layout/spacing changes around the photos row are reasonable.

The updated container padding (tw-pt-6 tw-pb-10) and always-rendered photos row (backed by printDistributionPhotos() returning an empty fragment when there are no photos) should produce a stable layout with or without photos. Just confirm during QA that the extra vertical space when there are no photos matches the desired design.

Comment thread components/user/subscriptions/UserPageSubscriptionsTopUp.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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 (3)
components/distribution/Distribution.tsx (1)

154-177: Unify getSpotsForPhase to return JSX.Element | null instead of mixing strings and JSX

Right now getSpotsForPhase can return "", a <span>, or null, which complicates typing and is slightly confusing when consumed in JSX. It would be clearer to always return either a JSX element or null:

-  function getSpotsForPhase(d: Distribution, phase: string) {
-    if (phase.toUpperCase() === "AIRDROP") {
-      return "";
-    }
+  function getSpotsForPhase(
+    d: Distribution,
+    phase: string
+  ): JSX.Element | null {
+    if (phase.toUpperCase() === "AIRDROP") {
+      return null;
+    }
@@
-      if (!spotsAirdrop && !spotsAllowlist) {
-        return "";
-      }
+      if (!spotsAirdrop && !spotsAllowlist) {
+        return null;
+      }
@@
-    }
-    return null;
+    }
+    return null;
   }

This keeps the visual behavior the same but gives you a predictable return type.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

9-19: Deduplicate extractAllNumbers and tighten its typing

This helper still lives locally and is very likely duplicated in ReviewDistributionPlanTableSubscriptionFooter.tsx. Extracting it to a shared utility (e.g. helpers/Helpers.ts) will remove duplication and make tests/maintenance easier. While doing that, give it explicit types to avoid implicit any.

Example:

- function extractAllNumbers(str: string): number[] {
-  const regex = /\d+/g;
-  const numbers = [];
-  let match;
-
-  while ((match = regex.exec(str)) !== null) {
-    numbers.push(Number.parseInt(match[0], 10));
-  }
-
-  return numbers;
- }
+ function extractAllNumbers(str: string): number[] {
+  const regex = /\d+/g;
+  const numbers: number[] = [];
+  let match: RegExpExecArray | null;
+
+  while ((match = regex.exec(str)) !== null) {
+    numbers.push(Number.parseInt(match[0], 10));
+  }
+
+  return numbers;
+ }

Then move this implementation into helpers/Helpers.ts, export it, and import it here and in ReviewDistributionPlanTableSubscriptionFooter.tsx to stay DRY.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

359-392: Replace document.querySelector + setTimeout with a ref for the “Other” input

The “Other” option still uses document.querySelector('input[placeholder="count"]') and setTimeout in both onClick and radio onChange. This is fragile (coupled to placeholder text and DOM structure), duplicates code, and bypasses React’s ref system. This matches the earlier bot comment and should be refactored.

-import { useCallback, useEffect, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
@@
   const [error, setError] = useState<string>("");
   const [showDeep, setShowDeep] = useState(false);
   const [mounted, setMounted] = useState(false);
   const [topUpAmount, setTopUpAmount] = useState<number | null>(null);
   const [selectedOption, setSelectedOption] = useState<string | null>(null);
+  const otherInputRef = useRef<HTMLInputElement | null>(null);
@@
-          <button
+          <button
             type="button"
@@
-            onClick={() => {
-              setSelectedOption("other");
-              setError("");
-              setTimeout(() => {
-                const input = document.querySelector(
-                  'input[placeholder="count"]'
-                ) as HTMLInputElement;
-                input?.focus();
-              }, 0);
-            }}>
+            onClick={() => {
+              setSelectedOption("other");
+              setError("");
+              otherInputRef.current?.focus();
+            }}>
@@
                 <Form.Check
                   type="radio"
                   checked={selectedOption === "other"}
                   onChange={() => {
                     setSelectedOption("other");
                     setError("");
-                    setTimeout(() => {
-                      const input = document.querySelector(
-                        'input[placeholder="count"]'
-                      ) as HTMLInputElement;
-                      input?.focus();
-                    }, 0);
+                    otherInputRef.current?.focus();
                   }}
@@
-                <Form.Control
+                <Form.Control
+                  ref={otherInputRef}
                   type="number"
🧹 Nitpick comments (10)
components/mint-countdown-box/MintCountdownBox.tsx (3)

51-65: Fix creates inconsistency with other status messages.

The error state correctly uses FontAwesome (addressing the past review), but now differs from the "Sold Out" (line 70: ✅) and "Mint Phase Complete" (line 82: ⌛️) states which still use emojis. For full consistency with coding guidelines, all status indicators should use FontAwesome.

Additionally, line 57 hardcodes the color #FFD60A instead of using a Tailwind color utility class.

Apply this diff to use a Tailwind color class:

         <FontAwesomeIcon
           icon={faExclamationTriangle}
-          color="#FFD60A"
-          className="tw-h-6 tw-w-6"
+          className="tw-h-6 tw-w-6 tw-text-yellow-400"
         />

67-87: Replace remaining emojis with FontAwesome icons for consistency.

The "Sold Out" and "Mint Phase Complete" states still use emojis (✅ and ⌛️) while the error state now uses FontAwesome. Per coding guidelines, all icons should be FontAwesome components.

Consider applying this pattern to use FontAwesome icons throughout:

+import { faCheckCircle, faClock } from "@fortawesome/free-solid-svg-icons";

 if (mintInfo?.isSoldOut) {
   return (
     <div className={containerClasses}>
-      <h4 className="mb-3">✅ Mint Complete - Sold Out!</h4>
+      <h4 className="mb-3 tw-flex tw-items-center tw-gap-2">
+        <FontAwesomeIcon icon={faCheckCircle} className="tw-h-6 tw-w-6 tw-text-green-500" />
+        Mint Complete - Sold Out!
+      </h4>
       <p className="mb-1 tw-font-medium">
         All NFTs have been successfully minted.
       </p>
       <p className="mb-0 tw-text-md">Thank you for participating!</p>
     </div>
   );
 }

 if (mintInfo?.isFinalized) {
   return (
     <div className={containerClasses}>
-      <h4 className="mb-3">⌛️ Mint Phase Complete</h4>
+      <h4 className="mb-3 tw-flex tw-items-center tw-gap-2">
+        <FontAwesomeIcon icon={faClock} className="tw-h-6 tw-w-6 tw-text-gray-400" />
+        Mint Phase Complete
+      </h4>
       <p className="mb-1 tw-font-medium">This mint phase has ended.</p>
       <p className="mb-0 tw-text-md">Thank you for participating!</p>
     </div>
   );
 }

32-89: Remove code comments per coding guidelines.

The coding guidelines specify: "Do not include any comments in the code; it should be self-explanatory." Please remove comments on lines 32, 38, and 89.

Apply this diff:

-  // Derived flags
   const hasMintInfo = Boolean(mintInfo);

-  // Safe date conversion (only when present)
   const countdownDate =

-  // Default: minting state
   return (

Based on coding guidelines, the code should be self-explanatory through clear variable and function names.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (1)

90-105: Public phase logic is correct.

The admin-gated public phase row is properly structured and conditionally added based on user permissions.

Consider extracting the hardcoded strings "Public" and "Auto-generated" to named constants for better maintainability and potential i18n support.

For example:

const PUBLIC_PHASE_NAME = "Public";
const PUBLIC_PHASE_DESCRIPTION = "Auto-generated";
components/distribution/Distribution.tsx (2)

6-6: Tighten isValidNftId so only purely numeric ids are accepted

The early NotFound guard is helpful, but parseInt will treat values like "123abc" as valid, which could unintentionally map to another card. If you want the route id to be strictly numeric, consider validating the full string:

-  const isValidNftId = !Number.isNaN(Number.parseInt(params?.id as string, 10));
-  const nftId = isValidNftId ? (params?.id as string) : "";
+  const routeId = (params?.id as string) ?? "";
+  const isValidNftId = /^\d+$/.test(routeId);
+  const nftId = isValidNftId ? routeId : "";

This keeps the NotFound behavior but ensures only digit-only ids are treated as valid.

Also applies to: 43-44, 318-320


242-246: Avoid making <td> a flex container; wrap content in a flex child instead

Applying Tailwind’s tw-flex directly to <td> changes its display from table-cell to flex, which can interfere with table layout and column sizing. It is safer to keep the cell as a table-cell and flex only the inner content:

-                      {distributionsPhases.map((p) => (
-                        <td
-                          key={`${p}-${d.wallet}`}
-                          className="tw-flex tw-justify-center tw-items-center">
-                          {getCountForPhase(d, p)}&nbsp;&nbsp;
-                          {getSpotsForPhase(d, p)}
-                        </td>
-                      ))}
+                      {distributionsPhases.map((p) => {
+                        const spots = getSpotsForPhase(d, p);
+                        return (
+                          <td
+                            key={`${p}-${d.wallet}`}
+                            className="tw-text-center">
+                            <span className="tw-inline-flex tw-items-center tw-justify-center">
+                              <span>{getCountForPhase(d, p)}</span>
+                              {spots && <span className="tw-ml-2">{spots}</span>}
+                            </span>
+                          </td>
+                        );
+                      })}

This preserves the alignment you want while keeping proper table semantics.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

157-160: Use FontAwesome + Tailwind for the warning instead of a raw emoji + Bootstrap alert

The warning block currently mixes a literal emoji with Bootstrap classes:

<div className="alert alert-warning mb-0 border border-dark">
  ⚠️ {props.existingPhotosCount} photo(s) already exist. This
  will replace all existing photos.
</div>

To align with the guidelines (FontAwesome for icons, Tailwind for styling), consider switching to a FontAwesome icon and Tailwind utility classes, for example:

<div className="tw-mb-0 tw-flex tw-items-center tw-gap-2 tw-rounded tw-border tw-border-yellow-500 tw-bg-yellow-100 tw-p-2 tw-text-yellow-900">
  <FontAwesomeIcon icon={faTriangleExclamation} />
  <span>
    {props.existingPhotosCount} photo(s) already exist. This will replace all existing photos.
  </span>
</div>

Remember to add the necessary FontAwesome imports at the top of the file if they are not already present.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (3)

126-138: Surface waitSendTransaction.error so reverted/dropped txs also show an error modal

Right now only sendTransaction.error is mapped to error, so failures during waitSendTransaction (e.g. tx revert or dropped/replaced) will silently clear the modal with no explanation. Mirror the same handling for waitSendTransaction.error so all tx failures are visible and closable via the existing modal.

   useEffect(() => {
     if (sendTransaction.error) {
       const errorMsg =
         sendTransaction.error.message.split("Request Arguments")[0];
       setError(`Error - ${errorMsg}`);
     }
   }, [sendTransaction.error]);
+
+  useEffect(() => {
+    if (waitSendTransaction.error) {
+      const errorMsg =
+        waitSendTransaction.error.message.split("Request Arguments")[0];
+      setError(`Error - ${errorMsg}`);
+    }
+  }, [waitSendTransaction.error]);

254-265: Avoid nesting a <button> inside next/link for external navigation

For the iOS CTA you’re using next/link (which renders an <a>) wrapping a <button>, which is invalid interactive nesting and doesn’t buy you much for an external href. Given the guideline is to use <Link> for internal routes, it’s cleaner to use a styled <a> here.

-  const iOsContent = (
-    <Row className="pt-2">
-      <Col>
-        <Link
-          href={window.location.href}
-          className="text-center pt-2 pb-2"
-          target="_blank"
-          rel="noopener noreferrer">
-          <button className="btn btn-light" style={{ width: "100%" }}>
-            Top-up on 6529.io
-          </button>
-        </Link>
-      </Col>
-    </Row>
-  );
+  const iOsContent = (
+    <Row className="pt-2">
+      <Col>
+        <a
+          href={window.location.href}
+          className="btn btn-light text-center pt-2 pb-2"
+          style={{ width: "100%" }}
+          target="_blank"
+          rel="noopener noreferrer">
+          Top-up on 6529.io
+        </a>
+      </Col>
+    </Row>
+  );

471-479: Tooltip shows the identifier name instead of the actual subscription address

The tooltip currently renders the literal string SUBSCRIPTIONS_ADDRESS, which is less useful than the actual address value. Consider using the constant so the user sees the real contract address on hover.

-                <Tooltip
+                <Tooltip
                   id="subscription-address"
                   style={{
                     backgroundColor: "#1F2937",
                     color: "white",
                     padding: "4px 8px",
                   }}>
-                  SUBSCRIPTIONS_ADDRESS
+                  {SUBSCRIPTIONS_ADDRESS}
                 </Tooltip>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4a87c3 and 3b9847c.

📒 Files selected for processing (6)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (2 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1 hunks)
  • components/distribution/Distribution.tsx (9 hunks)
  • components/mint-countdown-box/MemePageMintCountdown.tsx (3 hunks)
  • components/mint-countdown-box/MintCountdownBox.tsx (3 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/mint-countdown-box/MemePageMintCountdown.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Enforce ≥ 80% line coverage for files changed since main via npm run test
All code must pass ESLint (Next's Core Web Vitals + React Hooks rules) via npm run lint
Use <Link> from Next.js for internal navigation instead of <a> tags or HTML anchors
Use <Image> from next/image instead of HTML <img> elements

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: All code must pass TypeScript type checking via npm run type-check (tsc --noEmit)
Use 'use cache' directive at the top of Server Components or functions to explicitly opt-in to caching in Next.js 16

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-25T08:35:58.721Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.721Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/mint-countdown-box/MintCountdownBox.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `<Link>` from Next.js for internal navigation instead of `<a>` tags or HTML anchors

Applied to files:

  • components/mint-countdown-box/MintCountdownBox.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Fix issues with modernization aligned to React 19.2, React Compiler, and Next.js 16 conventions; do not add `// eslint-disable` comments unless explicitly instructed

Applied to files:

  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Use TypeScript and React functional components with hooks

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx
🧬 Code graph analysis (1)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (5)
scripts/dev-open.cjs (1)
  • match (43-43)
components/allowlist-tool/allowlist-tool.types.ts (1)
  • AllowlistDescription (12-18)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
__mocks__/react-use.js (1)
  • React (2-2)
helpers/Helpers.ts (1)
  • formatAddress (31-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (9)
components/mint-countdown-box/MintCountdownBox.tsx (2)

1-4: Good addition of FontAwesome icon import.

The import of faExclamationTriangle properly addresses the previous review feedback to use FontAwesome icons instead of emojis for the error state.


11-30: LGTM! Props and destructuring are correctly implemented.

The optional isError prop is properly typed, and the component correctly uses Readonly<Props> as required by coding guidelines.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTable.tsx (4)

3-11: LGTM! Imports are correctly organized.

All new imports are necessary and properly structured for the new admin-gated public phase functionality.


108-108: LGTM! useEffect dependencies are correct.

Both phases and connectedProfile are properly included in the dependency array, ensuring the effect runs when either value changes.


51-86: I'll help you verify the phase description concern in the review comment. Let me search the codebase to understand the phase object structure and whether the description field should be populated.
<function_calls>


#!/bin/bash

Find the file under review

fd -t f "ReviewDistributionPlanTable.tsx"


</function_calls>
<function_calls>


#!/bin/bash

Search for ReviewDistributionPlanTableItem interface/type definition

rg -n "ReviewDistributionPlanTableItem" --type ts --type tsx -A 15


</function_calls>
<function_calls>


#!/bin/bash

Search for phase type definitions in the component files

rg -n "interface.*Phase|type.*Phase" --type ts --type tsx -A 10 | head -100


</function_calls>


46-46: Perfect! I found the implementation. The isSubscriptionsAdmin function is properly designed to handle null/undefined values:

export const isSubscriptionsAdmin = (connectedProfile: ApiIdentity | null) => {
  const connectedWallets =
    connectedProfile?.wallets?.map((wallet) => wallet.wallet) ?? [];
  return connectedWallets.some((w) =>
    SUBSCRIPTIONS_ADMIN_WALLETS.some((a) => areEqualAddresses(a, w))
  );
};

Key safety mechanisms:

  • Parameter type explicitly allows null (ApiIdentity | null)
  • Uses optional chaining (?.) to safely access nested properties
  • Uses nullish coalescing (??) to default to an empty array if the chain breaks
  • .some() on an empty array safely returns false

No runtime errors will occur when connectedProfile is null or undefined. The original review comment's concern is not valid - proper null-safety is already in place.

components/distribution/Distribution.tsx (2)

268-272: Minting link guard now correctly enforces contract + numeric id

The updated condition in printMintingLink (checking MEMES_CONTRACT, isValidNftId, and nftId) combined with Number.parseInt(nftId, 10) is a solid improvement over the previous behavior and avoids rendering the countdown when the route id is invalid or the contract does not match.


13-13: UpcomingMemePage integration in the empty state looks consistent

Importing and rendering UpcomingMemePage in printEmpty wired to id={nftId} fits the new empty-state UX, and thanks to the early NotFound guard it will only run when the id is considered valid.

Also applies to: 281-283

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

458-537: Modal + CardCountOption implementation looks solid and aligns with guidelines

The modal portal is correctly gated behind mounted to avoid document.body SSR issues, the animation/overlay logic is clean, and the top‑up summary text now renders with proper spacing. CardCountOption uses Readonly props, Tailwind, and radio semantics appropriately, and the pluralization/ETH math reads clearly. This matches the React‑with‑hooks and FontAwesome/Tailwind expectations from the learnings.

Also applies to: 542-582

Comment thread components/distribution/Distribution.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

18-20: Harden tokenId validation to only accept positive integer IDs and reuse it across the component.

Right now parseInt-based checks still treat "1.5" or "1abc" as valid (because parseInt stops at the first non‑digit). This can let malformed token IDs through, and the same logic is duplicated in both the upload guard and the button’s disabled state. Also, adding step={1} helps discourage fractional values in the UI. This aligns with the earlier review feedback on this flow.

You can centralize validation and tighten the checks like this:

   const [tokenId, setTokenId] = useState<string>(
     extractAllNumbers(props.plan.name)[0]?.toString() ?? ""
   );
+  const isValidTokenId = (value: string): boolean =>
+    /^[1-9]\d*$/.test(value.trim());
@@
   const handleUpload = () => {
@@
-    if (!tokenId || Number.isNaN(Number.parseInt(tokenId, 10))) {
+    if (!isValidTokenId(tokenId)) {
       return;
     }
@@
               <input
@@
-                min={1}
-                type="number"
-                value={tokenId}
+                min={1}
+                step={1}
+                type="number"
+                value={tokenId}
                 onChange={(e) => {
                   setTokenId(e.target.value);
                 }}
@@
         <Button
-          disabled={
-            !tokenId ||
-            Number.isNaN(Number.parseInt(tokenId, 10)) ||
-            selectedFiles.length === 0
-          }
+          disabled={!isValidTokenId(tokenId) || selectedFiles.length === 0}

Also applies to: 88-90, 127-138, 207-211

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)

135-138: Fix tokenId input binding and tighten validation to only allow positive integer IDs.

value={Number.parseInt(tokenId, 10)} will be NaN when tokenId is "", which leads to React warnings and awkward UX on the number input. The parseInt-based disabled check also allows malformed values like "1.5" or "1abc". This is the same pattern previously called out on related components and should be unified here as well.

You can keep tokenId as a string, validate it via a helper, and reuse that helper for the disabled state:

 export function SubscriptionConfirm(
@@
   const contract = MEMES_CONTRACT;
   const numbers = extractAllNumbers(props.plan.name);
   const [tokenId, setTokenId] = useState<string>(
     numbers.length > 0 ? numbers[0].toString() : ""
   );
+  const isValidTokenId = (value: string): boolean =>
+    /^[1-9]\d*$/.test(value.trim());
@@
               <input
@@
                 min={1}
-                type="number"
-                value={Number.parseInt(tokenId, 10)}
+                type="number"
+                step={1}
+                value={tokenId}
                 onChange={(e) => {
                   setTokenId(e.target.value);
                 }}
@@
         </Button>
         <Button
-          disabled={!tokenId || Number.isNaN(Number.parseInt(tokenId, 10))}
+          disabled={!isValidTokenId(tokenId)}
           variant="primary"
           onClick={() => props.onConfirm(contract, tokenId)}>

Also applies to: 157-169, 188-191

🧹 Nitpick comments (5)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

550-556: Radio button inside button causes double handler invocation.

When clicking directly on the radio input, both the radio's onChange and the wrapping button's onClick fire, calling props.onSelect twice. While harmless since the operation is idempotent, you could prevent the redundant call.

       <button
         type="button"
         className={`tw-border tw-rounded-lg tw-p-3 tw-cursor-pointer tw-transition-colors tw-w-full tw-text-left ${
           styles.cardCountOption
         } ${
           props.selected
             ? "tw-bg-iron-700"
             : "tw-bg-transparent hover:tw-bg-iron-900"
         }`}
         onClick={props.onSelect}>
         <Row className="d-flex align-items-center">
           <Col xs={1} className="d-flex tw-justify-center">
             <Form.Check
               type="radio"
               checked={props.selected}
-              onChange={props.onSelect}
+              onChange={() => {}}
               style={{
                 accentColor: "#406AFE",
                 width: "20px",
                 height: "20px",
               }}
               className={`tw-scale-125 ${styles.radioButton}`}
             />
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

141-151: Consider replacing the literal warning emoji with a FontAwesome icon for consistency.

Given the project’s styling guidelines for TSX/JSX, you may want to swap the raw ⚠️ marker and text-only remove actions for FontAwesome icons (e.g., an exclamation/triangle and a trash/remove icon) to keep iconography consistent across the app.

Also applies to: 176-199

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (2)

20-37: Clear overview when no valid distribution/token is available to avoid stale UI.

If distributionPlan becomes invalid or its name no longer yields a numeric tokenId, the effect early‑returns but leaves the previous overview in state. That can show an out‑of‑date photo count/normalized flag for the new plan. You can reset the overview in the guard paths:

   useEffect(() => {
     async function fetchOverview() {
-      if (!distributionPlan || !isSubscriptionsAdmin(connectedProfile)) {
-        return;
-      }
+      if (!distributionPlan || !isSubscriptionsAdmin(connectedProfile)) {
+        setOverview(null);
+        return;
+      }
@@
-      const tokenId = extractAllNumbers(distributionPlan.name)[0]?.toString();
-
-      if (!tokenId) {
-        return;
-      }
+      const tokenId = extractAllNumbers(distributionPlan.name)[0]?.toString();
+
+      if (!tokenId) {
+        setOverview(null);
+        return;
+      }

Also applies to: 38-62


143-164: Swap the normalization status emojis for FontAwesome icons to align with icon guidelines.

Using literal "✅"/"❌" works visually but diverges from a FontAwesome-based icon system. Consider substituting with appropriate FontAwesome icons (e.g., check/times or check/circle variants) for better consistency and theming control.

Also applies to: 155-160

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (1)

219-242: download/CSV processing logic is sound; consider simplifying the async chain later.

The download function + processResults correctly map the API response into multiple CSV downloads, and the special handling for the "public" phase name is clear. At some point you might want to replace the await ... .then().catch() combination with a straight try/catch for readability, but behavior-wise this is fine as-is.

Also applies to: 244-258

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b9847c and df368ed.

📒 Files selected for processing (5)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (5 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (4 hunks)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
  • helpers/Helpers.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Enforce ≥ 80% line coverage for files changed since main via npm run test
All code must pass ESLint (Next's Core Web Vitals + React Hooks rules) via npm run lint
Use <Link> from Next.js for internal navigation instead of <a> tags or HTML anchors
Use <Image> from next/image instead of HTML <img> elements

Files:

  • helpers/Helpers.ts
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: All code must pass TypeScript type checking via npm run type-check (tsc --noEmit)
Use 'use cache' directive at the top of Server Components or functions to explicitly opt-in to caching in Next.js 16

Files:

  • helpers/Helpers.ts
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Fix issues with modernization aligned to React 19.2, React Compiler, and Next.js 16 conventions; do not add `// eslint-disable` comments unless explicitly instructed

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:35:58.721Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.721Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Use TypeScript and React functional components with hooks

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
🧬 Code graph analysis (2)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (4)
constants.ts (4)
  • MEMES_MINT_PRICE (74-74)
  • SUBSCRIPTIONS_CHAIN (61-61)
  • SUBSCRIPTIONS_ADDRESS_ENS (67-67)
  • SUBSCRIPTIONS_ADDRESS (65-66)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
helpers/Helpers.ts (3)
  • getTransactionLink (237-246)
  • formatAddress (31-46)
  • numberWithCommasFromString (72-81)
components/show-more-button/ShowMoreButton.tsx (1)
  • ShowMoreButton (7-35)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (3)
components/allowlist-tool/allowlist-tool.types.ts (1)
  • AllowlistDescription (12-18)
helpers/Helpers.ts (2)
  • extractAllNumbers (83-93)
  • formatAddress (31-46)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
🔇 Additional comments (8)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (3)

44-89: LGTM on state management and ref-based focus handling.

The state declarations and handleSelectOther callback properly address the previous review feedback by using useRef instead of document.querySelector. The mounted state correctly guards against SSR issues with the portal.


149-183: LGTM on modal lifecycle management.

The body scroll lock, escape key handler, and closeModal callback are properly implemented with appropriate cleanup in the useEffect returns. Using globalThis.addEventListener is SSR-safe.


185-255: LGTM on modal content rendering.

The getModalContent function appropriately handles all transaction states with clear visual feedback. External etherscan links correctly use <a> tags with target="_blank" and rel="noopener noreferrer".

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

27-56: File validation and reset behavior look solid.

Type/size checks, clear error messaging, and resetting both selectedFiles and the underlying <input> cover the common UX pitfalls for multi-file uploads nicely.

Also applies to: 58-81

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooter.tsx (1)

185-203: Upload flow + overview refetch is well-structured.

The upload callback cleanly manages modal visibility, loading state, success/error toasts, and a follow-up overview refetch; this keeps the footer UI in sync with backend photo state.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscription.tsx (3)

44-52: Admin + PHASE gating and public vs non‑public button split look good.

Early‑out when not subscriptions admin or not a PHASE, plus the isPublic branch that changes labels/titles, is a clear way to separate public and private subscription flows without complicating the parent table.

Also applies to: 53-121


172-181: Normalized-warning block is a useful guardrail.

The conditional warning when isNormalized is true gives admins an explicit heads-up that re-running normalization will overwrite existing data, which should reduce accidental recalculations.


211-217: Subscriptions admin check is well-scoped.

Using SUBSCRIPTIONS_ADMIN_WALLETS plus areEqualAddresses against all connected wallets is a clear, case-insensitive admin gate and keeps this concern nicely localized.

Comment thread components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
Comment thread helpers/Helpers.ts
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)

461-469: LGTM!

The tooltip now correctly displays the actual subscription address value using {SUBSCRIPTIONS_ADDRESS} instead of the literal string.

🧹 Nitpick comments (4)
components/distribution/Distribution.tsx (1)

154-177: Consider unifying return types for consistency.

The function returns "" (empty string) on lines 156 and 165, but null on line 176. While React renders both as nothing, returning null consistently would be cleaner for type inference.

   function getSpotsForPhase(d: Distribution, phase: string) {
     if (phase.toUpperCase() === "AIRDROP") {
-      return "";
+      return null;
     }
     const p = d.allowlist.find((a) => a.phase === phase);
     const count = p?.spots ?? 0;
     if (count > 0) {
       const spotsAirdrop = p?.spots_airdrop ?? 0;
       const spotsAllowlist = p?.spots_allowlist ?? 0;

       if (!spotsAirdrop && !spotsAllowlist) {
-        return "";
+        return null;
       }
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1)

172-179: Consider using a more stable key for error messages.

Using the error string as a key works for unique messages but could cause issues if duplicate error messages occur. Consider using an index-based key.

-                  {fileErrors.map((error) => (
-                    <div key={error} className="text-danger">
+                  {fileErrors.map((error, index) => (
+                    <div key={index} className="text-danger">
                       {error}
                     </div>
                   ))}
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (2)

532-573: Prevent potential double-firing of onSelect.

Both the outer <button> onClick (line 550) and the inner Form.Check onChange (line 556) call props.onSelect. When clicking the radio button, the event bubbles to the parent button, potentially calling onSelect twice.

           <Form.Check
             type="radio"
             checked={props.selected}
-            onChange={props.onSelect}
+            onChange={(e) => e.stopPropagation()}
             style={{
               accentColor: "#406AFE",
               width: "20px",
               height: "20px",
             }}
             className={`tw-scale-125 ${styles.radioButton}`}
           />

The parent button already handles the click, so the radio's onChange can just stop propagation. The checked prop will keep it in sync.


376-388: Same double-firing concern as CardCountOption.

The "Other" option button (line 375) and its radio (line 381) both call handleSelectOther. Apply the same fix to prevent double execution.

               <Form.Check
                 type="radio"
                 checked={selectedOption === "other"}
-                onChange={handleSelectOther}
+                onChange={(e) => e.stopPropagation()}
                 style={{
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df368ed and bf85cb3.

📒 Files selected for processing (4)
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (1 hunks)
  • components/distribution/Distribution.tsx (9 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
  • helpers/Helpers.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Enforce ≥ 80% line coverage for files changed since main via npm run test
All code must pass ESLint (Next's Core Web Vitals + React Hooks rules) via npm run lint
Use <Link> from Next.js for internal navigation instead of <a> tags or HTML anchors
Use <Image> from next/image instead of HTML <img> elements

Files:

  • helpers/Helpers.ts
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: All code must pass TypeScript type checking via npm run type-check (tsc --noEmit)
Use 'use cache' directive at the top of Server Components or functions to explicitly opt-in to caching in Next.js 16

Files:

  • helpers/Helpers.ts
  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx
  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Fix issues with modernization aligned to React 19.2, React Compiler, and Next.js 16 conventions; do not add `// eslint-disable` comments unless explicitly instructed

Applied to files:

  • components/distribution/Distribution.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `<Image>` from `next/image` instead of HTML `<img>` elements

Applied to files:

  • components/distribution/Distribution.tsx
📚 Learning: 2025-11-25T08:35:58.721Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.721Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:37:14.939Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:14.939Z
Learning: Use TypeScript and React functional components with hooks

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
🧬 Code graph analysis (3)
components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (3)
components/allowlist-tool/allowlist-tool.types.ts (1)
  • AllowlistDescription (12-18)
helpers/Helpers.ts (3)
  • extractAllNumbers (83-93)
  • isValidPositiveInteger (95-109)
  • formatAddress (31-46)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
components/distribution/Distribution.tsx (4)
helpers/Helpers.ts (2)
  • numberWithCommas (111-128)
  • areEqualAddresses (177-182)
constants.ts (1)
  • MEMES_CONTRACT (5-5)
components/the-memes/UpcomingMemePage.tsx (1)
  • UpcomingMemePage (4-14)
components/not-found/NotFound.tsx (1)
  • NotFound (7-51)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (1)
constants.ts (4)
  • MEMES_MINT_PRICE (74-74)
  • SUBSCRIPTIONS_CHAIN (61-61)
  • SUBSCRIPTIONS_ADDRESS_ENS (67-67)
  • SUBSCRIPTIONS_ADDRESS (65-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
helpers/Helpers.ts (2)

83-93: LGTM!

The extractAllNumbers function is correctly implemented. The match variable is now properly typed as RegExpExecArray | null, addressing the previous review comment.


95-109: LGTM!

The isValidPositiveInteger function properly validates both number and string inputs, correctly rejecting edge cases like decimals ("1.5"), leading zeros ("01"), and mixed strings ("1abc"). The num.toString() === value.trim() check ensures strict validation.

components/distribution/Distribution.tsx (4)

43-44: LGTM!

The validation logic correctly uses Number.parseInt with radix 10 to determine if the route parameter is a valid numeric ID. The derived nftId defaults to an empty string when invalid, preventing downstream issues.


110-139: LGTM!

The carousel implementation correctly limits image preloading to the first slide with priority={index === 0}, and the width/height props are now properly typed as numbers. The empty fragment return for no photos is acceptable.


278-308: LGTM!

The empty state now correctly integrates UpcomingMemePage with the nftId. The external link to X/Twitter appropriately uses an <a> tag with proper security attributes (target="_blank" and rel="noopener noreferrer").


318-320: LGTM!

Clean early return pattern for invalid NFT IDs, correctly rendering the NotFound component with an appropriate label.

components/distribution-plan-tool/review-distribution-plan/table/ReviewDistributionPlanTableSubscriptionFooterUploadPhotos.tsx (3)

22-26: LGTM!

The token ID extraction correctly uses the shared extractAllNumbers helper and validates the initial value with isValidPositiveInteger. This addresses previous review comments about DRY principles and proper validation.


33-62: LGTM!

File validation is well-implemented with efficient Set lookups for MIME type checking and comprehensive error messaging for both type and size violations.


88-104: LGTM!

The upload handler properly validates both file selection and token ID before proceeding. Using isValidPositiveInteger ensures robust validation that rejects malformed values.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (4)

76-89: LGTM!

State management is well-structured. The useRef pattern correctly replaces the fragile document.querySelector approach from previous code.


91-131: LGTM!

The handleSend function correctly validates the selected option, computes the transaction value, and handles both predefined and custom count options.


149-183: LGTM!

The modal lifecycle is well-managed with proper body scroll locking, escape key handling, and cleanup. The closeModal callback correctly resets all relevant state.


185-255: LGTM!

The getModalContent function provides clear visual feedback for all transaction states (error, pending confirmation, submitted/loading, success) with appropriate loading indicators and transaction links.

Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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 (3)
components/user/subscriptions/UserPageSubscriptions.module.scss (1)

152-168: Focus indicator concern has been addressed.

The :focus-visible pseudo-class now provides a visible outline for keyboard navigation while :focus:not(:focus-visible) hides it for mouse interactions. This meets WCAG 2.4.7 Focus Visible requirements.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (2)

461-469: Tooltip issue has been addressed.

The tooltip now correctly displays the actual {SUBSCRIPTIONS_ADDRESS} value instead of the literal variable name.


500-504: Text spacing issue has been addressed.

The display text now correctly includes proper spacing between the card count and "Cards".

🧹 Nitpick comments (4)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (4)

554-560: Potential double event trigger when clicking the radio directly.

When the user clicks directly on the radio input, both the onChange handler and the button's onClick handler will fire (due to event bubbling), causing onSelect to be called twice. While this may be benign for simple state toggles, it can cause unexpected behavior.

Consider either stopping propagation or removing the radio's onChange:

🔎 Suggested fix
           <input
             type="radio"
             checked={props.selected}
-            onChange={props.onSelect}
+            onChange={() => {}}
             aria-label={labelText}
             className={styles.radioInput}
           />

382-388: Same double-trigger pattern as CardCountOption.

The radio's onChange and the parent button's onClick both call handleSelectOther. For consistency with any fix applied to CardCountOption, consider the same approach here.


481-492: Consider adding focus trap for modal accessibility.

The modal implementation has good features (escape key handling, backdrop click, aria-label on close button), but it lacks focus trapping. For full keyboard accessibility, focus should be contained within the modal while it's open.

Consider using a library like @radix-ui/react-dialog or react-focus-lock to handle focus trapping, or implement it manually by focusing the first interactive element on open and trapping Tab/Shift+Tab navigation.


281-304: Minor: Consider renaming or extracting printRemainingMints.

This function returns JSX and acts like a component. Consider either:

  1. Extracting it as a proper component (RemainingMintsOption)
  2. Renaming to renderRemainingMints to follow React conventions for render methods

This is a minor naming convention suggestion.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8084d3 and 2d184e1.

📒 Files selected for processing (2)
  • components/user/subscriptions/UserPageSubscriptions.module.scss (2 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
🧠 Learnings (4)
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Test accessibility with keyboard navigation and screen reader compatibility

Applied to files:

  • components/user/subscriptions/UserPageSubscriptions.module.scss
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/{components,contexts,hooks}/**/*.{ts,tsx} : Use semantic HTML elements (`<label>`, `<output>`) over ARIA attributes when possible; every form control must have a label

Applied to files:

  • components/user/subscriptions/UserPageSubscriptions.module.scss
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • components/user/subscriptions/UserPageSubscriptions.module.scss
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-11-25T08:35:58.729Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.729Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
🧬 Code graph analysis (1)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (4)
constants.ts (4)
  • MEMES_MINT_PRICE (74-74)
  • SUBSCRIPTIONS_CHAIN (61-61)
  • SUBSCRIPTIONS_ADDRESS_ENS (67-67)
  • SUBSCRIPTIONS_ADDRESS (65-66)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
helpers/Helpers.ts (3)
  • getTransactionLink (265-274)
  • formatAddress (32-47)
  • numberWithCommasFromString (73-82)
components/show-more-button/ShowMoreButton.tsx (1)
  • ShowMoreButton (7-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
components/user/subscriptions/UserPageSubscriptions.module.scss (1)

115-150: Accessibility improvements look good.

The custom radio input styling properly implements :focus-visible for keyboard accessibility, appearance: none for cross-browser consistency, and a clear visual checked state. This addresses previous concerns about focus indicators.

components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (3)

44-46: LGTM!

The getEthForCards helper correctly handles floating-point precision by rounding to avoid arithmetic issues with JavaScript decimals.


91-93: LGTM!

The mounted state pattern correctly prevents hydration mismatches when using createPortal in a Next.js environment.


265-279: Consider guarding window.location.href for SSR safety.

Although this code path is gated by isIos, the iOsContent JSX with window.location.href is evaluated during render regardless of whether it's displayed. This could cause issues during SSR or static generation.

Consider using a state-based approach or conditional evaluation:

🔎 Suggested fix
   const iOsContent = (
     <Row className="pt-2">
       <Col>
-        <Link
-          href={window.location.href}
+        <a
+          href={typeof window !== "undefined" ? window.location.href : "#"}
           className="text-center pt-2 pb-2"
           target="_blank"
           rel="noopener noreferrer">
           <button className="btn btn-light" style={{ width: "100%" }}>
             Top-up on 6529.io
           </button>
-        </Link>
+        </a>
       </Col>
     </Row>
   );

Note: Using <a> instead of <Link> is appropriate here since target="_blank" opens in a new tab (external navigation behavior).

Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (1)

89-89: Type mismatch: date prop can be null but type doesn't reflect it.

Line 89 passes rows[index] ?? null, but line 114 declares date: SeasonMintRow (non-nullable). This will cause TypeScript errors and potential runtime crashes when date is null since lines 267 and 289 access props.date.seasonIndex and props.date.utcDay without null checks.

🔎 Apply this diff to fix the type and add null guards:
 function SubscriptionRow(
   props: Readonly<{
     profileKey: string;
     title: string;
     subscription: NFTSubscription;
     eligibilityCount: number;
     readonly: boolean;
     minting_today?: boolean;
     first: boolean;
-    date: SeasonMintRow;
+    date: SeasonMintRow | null;
     refresh: () => void;
   }>
 ) {

Then guard the usages in the JSX:

-              <span>
-                - SZN{displayedSeasonNumberFromIndex(props.date.seasonIndex)}
-              </span>
+              {props.date && (
+                <span>
+                  - SZN{displayedSeasonNumberFromIndex(props.date.seasonIndex)}
+                </span>
+              )}
               ) : (
-                <span>{formatFullDate(props.date.utcDay)}</span>
+                <span>{props.date ? formatFullDate(props.date.utcDay) : "TBD"}</span>
               )}

Also applies to: 114-114

🧹 Nitpick comments (1)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (1)

196-202: Error handling improved, but error message extraction could be more robust.

The redundant setSubscribed call from the past review has been correctly removed. However, typeof e === "string" rarely matches API errors which are typically Error objects.

🔎 Consider this more robust error extraction:
     } catch (e: unknown) {
       setToast({
         message:
-          typeof e === "string" ? e : "Failed to change token subscription.",
+          e instanceof Error ? e.message : "Failed to change token subscription.",
         type: "error",
       });
       return;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d184e1 and dce03cb.

📒 Files selected for processing (1)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (4)

1-27: LGTM!

Imports are appropriate and organized. The new formatAddress import supports the address formatting feature added in the component.


137-141: Good implementation of the eligibility count guard.

This useEffect properly addresses the edge case where eligibilityCount decreases below selectedCount, preventing invalid UI states. The conditional ensures no infinite loop.


208-255: LGTM!

The handleUpdateSubscriptionCount function has proper error handling with state revert on failure. The isSubmitting guard prevents race conditions from rapid interactions.


315-344: Count selector implementation looks good.

The selector properly:

  • Disables when eligibilityCount <= 1 (line 319)
  • Generates options from 1 to eligibilityCount (lines 330-337)
  • Uses optimistic updates with proper revert on error

One minor consideration: rapid changes trigger multiple API calls. Consider debouncing if this becomes a UX issue.

Comment thread components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (1)

114-114: Fix type mismatch: date prop should be nullable.

Line 89 passes rows[index] ?? null, but the prop type at line 114 declares date: SeasonMintRow (non-nullable). This creates a TypeScript type error and will cause runtime crashes when accessing props.date.seasonIndex (line 267) or props.date.utcDay (line 289) without null guards.

🔎 Update the prop type to allow null:
   props: Readonly<{
     profileKey: string;
     title: string;
     subscription: NFTSubscription;
     eligibilityCount: number;
     readonly: boolean;
     minting_today?: boolean;
     first: boolean;
-    date: SeasonMintRow;
+    date: SeasonMintRow | null;
     refresh: () => void;
   }>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dce03cb and 976b0f3.

📒 Files selected for processing (1)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/{components,contexts,hooks}/**/*.{ts,tsx} : Use semantic HTML elements (`<label>`, `<output>`) over ARIA attributes when possible; every form control must have a label

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (1)

123-123: Consider explicit boolean conversion for clarity.

The !! operator works correctly but is less explicit than alternatives. Per coding guidelines preferring explicit logic, consider using Boolean() for clearer intent.

🔎 Apply this diff for more explicit boolean conversion:
-  const [subscribed, setSubscribed] = useState<boolean>(
-    !!props.subscription.subscribed
-  );
+  const [subscribed, setSubscribed] = useState<boolean>(
+    Boolean(props.subscription.subscribed)
+  );

Based on learnings from coding guidelines that recommend avoiding double negatives and preferring explicit logic.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 976b0f3 and a77ca72.

📒 Files selected for processing (3)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (6 hunks)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (7 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
**/@(__tests__|*.test).{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Tests should live in __tests__/ or ComponentName.test.tsx; mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Place tests in __tests__/ directory or as ComponentName.test.tsx alongside components

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use Jest + ts-jest for TypeScript testing

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Functions must have ≤ 15 cognitive complexity; extract deep ternaries (>3 levels) and break down complex logic

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (tests/AGENTS.md)

__tests__/**/*.{ts,tsx,js}: Prefer for...of loops over forEach as it allows break/continue and works with async/await
Use array.at(-1) and array.at(-2) instead of index-based array access for negative indexing
Use String.prototype.replaceAll() instead of replace() for global string replacements
Use globalThis.fetch() instead of direct fetch() calls
Organize imports with one import per module in order: external → internal → types, with no duplicates
Use element.remove() instead of parent.removeChild(element) for DOM manipulation
Catch errors only when meaningful; no empty catch blocks; log errors with context
Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (?.)

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/{components,contexts,hooks}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use semantic HTML elements (<label>, <output>) over ARIA attributes when possible; every form control must have a label

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
🧠 Learnings (8)
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/{components,contexts,hooks}/**/*.{ts,tsx} : Use semantic HTML elements (`<label>`, `<output>`) over ARIA attributes when possible; every form control must have a label

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/*.{ts,tsx,js} : Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (`?.`)

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Use testing-library/react + user-event for React component tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/*.{ts,tsx,js} : Use `String.prototype.replaceAll()` instead of `replace()` for global string replacements

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Applies to **/@(__tests__|*.test).{ts,tsx} : Tests should live in `__tests__/` or `ComponentName.test.tsx`; mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Test accessibility with keyboard navigation and screen reader compatibility

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-11-25T08:35:58.729Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.729Z
Learning: Applies to **/*.{tsx,jsx} : Use FontAwesome for icons in React components

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsTopUp.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
components/user/subscriptions/UserPageSubscriptionsTopUp.tsx (8)

44-46: LGTM!

The getEthForCards helper uses appropriate floating-point rounding to avoid precision issues when calculating ETH values.


80-93: LGTM!

The mounted state for portal hydration safety and useRef for input focus (addressing the previous querySelector concern) are implemented correctly.


95-135: LGTM!

The handleSend function properly validates the selected option, maps option IDs to card counts, and correctly calculates the ETH value using parseEther.


153-187: LGTM!

Body scroll locking, escape key handling, and modal close logic are properly implemented with appropriate cleanup in effect returns.


189-259: LGTM!

The getModalContent function properly handles all transaction states (error, pending, loading, success) with appropriate UI feedback and external transaction links.


368-444: LGTM!

The "Other" option implementation with useRef for input focus and proper event propagation handling is well done. The Send button's disabled logic correctly validates all edge cases.


532-574: LGTM!

The CardCountOption component is well-structured with readonly props, proper accessibility attributes, and clean selection handling.


477-527: LGTM!

The modal portal implementation with mounted guard for SSR safety, AnimatePresence for animations, and conditional close behavior based on isClosable is well-designed.

__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (1)

209-209: LGTM! Regex matcher improves test robustness.

The change from an exact string match to a regex pattern makes the test more flexible and better accommodates variations in whitespace or surrounding text while still verifying the essential "Minting Today" message appears.

components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (4)

18-18: LGTM! Appropriate helper import for address formatting.

The formatAddress import supports the new phase information display at line 302, where it formats the airdrop address for better readability.


89-89: Excellent work addressing all previous review concerns!

All five issues flagged in the previous review have been properly resolved:

  1. Line 196-206: Removed redundant setSubscribed(subscribed) call from catch block ✓
  2. Lines 137-141: Added useEffect to clamp selectedCount when eligibilityCount decreases ✓
  3. Lines 89, 114, 266-295: Added null handling for rows[index] with proper type guards and conditional rendering ✓
  4. Line 335: Added aria-label to the subscription count selector ✓
  5. Lines 266-295: Added comprehensive null guards for props.date access ✓

The implementations are clean, correct, and follow React best practices.

Also applies to: 114-114, 137-141, 196-206, 266-295, 335-335


126-141: LGTM! Well-implemented subscription count feature with optimistic UI.

The new subscription count selector demonstrates solid patterns:

  • State synchronization (lines 126-141): useEffect hooks properly sync selectedCount with external changes and clamp to eligibilityCount
  • Optimistic updates (lines 329-333): UI updates immediately while API call proceeds in background
  • Error recovery (line 245): Reverts to subscribedCount on failure with clear error toast
  • Race condition protection (line 211): isSubmitting guard prevents concurrent API calls
  • Accessibility (line 335): Proper aria-label for screen readers

The handleUpdateSubscriptionCount function correctly mirrors the structure of the existing submit function and integrates cleanly with the component's state management.

Also applies to: 208-255, 317-350


65-68: LGTM! Layout refactor improves maintainability and visual hierarchy.

The transition from table-based to flex-based layout brings several benefits:

  • Clearer structure (lines 65-68): Semibold header with horizontal rule provides strong visual separation
  • Flexible rendering (lines 71-93): Dynamic odd/even/last styling via CSS modules enables better theming
  • Responsive design (lines 260-306): Two-column layout with proper gap spacing adapts well to different screen sizes
  • Conditional information (lines 297-305): Phase details display only when available and for the first subscription, reducing clutter

The refactor maintains all existing functionality while improving code maintainability and visual presentation.

Also applies to: 71-99, 260-306

Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
components/user/subscriptions/UserPageSubscriptionsHistory.tsx (1)

263-269: Consider removing unnecessary type assertion.

The as Date assertion on line 267 is misleading since the coalesced timestamp value is likely a number or string, not a Date object. The new Date() constructor handles number/string timestamps correctly, so the assertion adds no value:

           {(props.redeem.transaction_date ?? props.redeem.created_at) &&
             getDateDisplay(
               new Date(
-                (props.redeem.transaction_date ??
-                  props.redeem.created_at) as Date
+                props.redeem.transaction_date ?? props.redeem.created_at
               )
             )}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a77ca72 and c598f33.

⛔ Files ignored due to path filters (33)
  • generated/models/AirdropAddressResponse.ts is excluded by !**/generated/**
  • generated/models/AirdropAddressResponseTdhWallet.ts is excluded by !**/generated/**
  • generated/models/AllowlistNormalizedEntry.ts is excluded by !**/generated/**
  • generated/models/ApiUpdateWaveRequest.ts is excluded by !**/generated/**
  • generated/models/DistributionNormalized.ts is excluded by !**/generated/**
  • generated/models/DistributionNormalizedPage.ts is excluded by !**/generated/**
  • generated/models/DistributionPhasesPage.ts is excluded by !**/generated/**
  • generated/models/DistributionPhoto.ts is excluded by !**/generated/**
  • generated/models/DistributionPhotosPage.ts is excluded by !**/generated/**
  • generated/models/GetRedeemedMemeSubscriptionCounts200Response.ts is excluded by !**/generated/**
  • generated/models/NFTFinalSubscription.ts is excluded by !**/generated/**
  • generated/models/NFTFinalSubscriptionUpload.ts is excluded by !**/generated/**
  • generated/models/NFTFinalSubscriptionUploadPage.ts is excluded by !**/generated/**
  • generated/models/NFTSubscription.ts is excluded by !**/generated/**
  • generated/models/ObjectSerializer.ts is excluded by !**/generated/**
  • generated/models/RedeemedSubscription.ts is excluded by !**/generated/**
  • generated/models/RedeemedSubscriptionCounts.ts is excluded by !**/generated/**
  • generated/models/RedeemedSubscriptionCountsPage.ts is excluded by !**/generated/**
  • generated/models/RedeemedSubscriptionPage.ts is excluded by !**/generated/**
  • generated/models/SubscribeAllEditionsResponse.ts is excluded by !**/generated/**
  • generated/models/SubscriptionCountResponse.ts is excluded by !**/generated/**
  • generated/models/SubscriptionCounts.ts is excluded by !**/generated/**
  • generated/models/SubscriptionDetails.ts is excluded by !**/generated/**
  • generated/models/SubscriptionLog.ts is excluded by !**/generated/**
  • generated/models/SubscriptionLogPage.ts is excluded by !**/generated/**
  • generated/models/SubscriptionModeResponse.ts is excluded by !**/generated/**
  • generated/models/SubscriptionResponse.ts is excluded by !**/generated/**
  • generated/models/SubscriptionTopUp.ts is excluded by !**/generated/**
  • generated/models/SubscriptionTopUpPage.ts is excluded by !**/generated/**
  • generated/models/UpdateSubscribeAllEditionsRequest.ts is excluded by !**/generated/**
  • generated/models/UpdateSubscriptionCountRequest.ts is excluded by !**/generated/**
  • generated/models/UpdateSubscriptionModeRequest.ts is excluded by !**/generated/**
  • generated/models/UpdateSubscriptionRequest.ts is excluded by !**/generated/**
📒 Files selected for processing (10)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx (1 hunks)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (3 hunks)
  • components/subscriptions-report/SubscriptionsReport.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptions.tsx (5 hunks)
  • components/user/subscriptions/UserPageSubscriptionsBalance.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (1 hunks)
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx (5 hunks)
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx (3 hunks)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (7 hunks)
  • entities/ISubscription.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • entities/ISubscription.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/user/subscriptions/UserPageSubscriptions.tsx
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/subscriptions-report/SubscriptionsReport.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
  • components/user/subscriptions/UserPageSubscriptionsBalance.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/subscriptions-report/SubscriptionsReport.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
  • components/user/subscriptions/UserPageSubscriptionsBalance.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/subscriptions-report/SubscriptionsReport.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
  • components/user/subscriptions/UserPageSubscriptionsBalance.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/subscriptions-report/SubscriptionsReport.tsx
  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
  • components/user/subscriptions/UserPageSubscriptionsBalance.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
  • components/user/subscriptions/UserPageSubscriptionsHistory.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
  • components/user/subscriptions/UserPageSubscriptionsMode.tsx
**/@(__tests__|*.test).{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Tests should live in __tests__/ or ComponentName.test.tsx; mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Place tests in __tests__/ directory or as ComponentName.test.tsx alongside components

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use Jest + ts-jest for TypeScript testing

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Functions must have ≤ 15 cognitive complexity; extract deep ternaries (>3 levels) and break down complex logic

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
__tests__/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (tests/AGENTS.md)

__tests__/**/*.{ts,tsx,js}: Prefer for...of loops over forEach as it allows break/continue and works with async/await
Use array.at(-1) and array.at(-2) instead of index-based array access for negative indexing
Use String.prototype.replaceAll() instead of replace() for global string replacements
Use globalThis.fetch() instead of direct fetch() calls
Organize imports with one import per module in order: external → internal → types, with no duplicates
Use element.remove() instead of parent.removeChild(element) for DOM manipulation
Catch errors only when meaningful; no empty catch blocks; log errors with context
Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (?.)

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
__tests__/**/{components,contexts,hooks}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use semantic HTML elements (<label>, <output>) over ARIA attributes when possible; every form control must have a label

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
🧠 Learnings (8)
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/*.{ts,tsx,js} : Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (`?.`)

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/{components,contexts,hooks}/**/*.{ts,tsx} : Use semantic HTML elements (`<label>`, `<output>`) over ARIA attributes when possible; every form control must have a label

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Applies to **/@(__tests__|*.test).{ts,tsx} : Tests should live in `__tests__/` or `ComponentName.test.tsx`; mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:30.871Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-05T10:55:30.871Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Use testing-library/react + user-event for React component tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Test accessibility with keyboard navigation and screen reader compatibility

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:30.871Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-05T10:55:30.871Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript and React functional components with hooks

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsMode.tsx
🧬 Code graph analysis (3)
components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (3)
generated/models/SubscriptionDetails.ts (1)
  • SubscriptionDetails (15-71)
components/auth/Auth.tsx (1)
  • AuthContext (83-93)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
components/user/subscriptions/UserPageSubscriptionsHistory.tsx (1)
helpers/Helpers.ts (3)
  • getDateDisplay (167-187)
  • formatAddress (32-47)
  • getTransactionLink (265-274)
components/user/subscriptions/UserPageSubscriptionsMode.tsx (1)
components/distribution-plan-tool/common/CircleLoader.tsx (1)
  • CircleLoader (9-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (19)
components/user/subscriptions/UserPageSubscriptionsMode.tsx (3)

84-96: LGTM! Clear timestamp display with proper UTC formatting.

The conditional rendering and date formatting are well-implemented. The explicit UTC timezone specification ensures consistent display regardless of the user's local timezone.


4-6: LGTM! CircleLoader integration follows established patterns.

The replacement of Spinner with CircleLoader is consistent with the broader UI updates across subscription components, and the size specification aligns with the component's API.

Also applies to: 116-116


80-127: LGTM! Layout updates maintain accessibility and align with design system.

The mixed Bootstrap/Tailwind approach is consistent with the broader refactoring. The terminology shift to "drops" and proper label associations maintain clarity and accessibility.

components/user/subscriptions/UserPageSubscriptionsEditionPreference.tsx (4)

34-77: LGTM! Error handling and async flow properly implemented.

The toggle logic correctly addresses all concerns from previous reviews:

  • requestAuth is called within the try block, ensuring cleanup happens even if it rejects
  • Error messages are safely extracted with proper fallbacks
  • State cleanup is handled exclusively in the finally block, avoiding redundancy

27-28: LGTM! Eligibility count display with sensible default.

The fallback to 1 for eligibility count is appropriate, and the display format clearly communicates the multiplier to users.

Also applies to: 86-86


30-32: LGTM! Effect properly scoped to specific prop.

The dependency array correctly tracks only the specific nested property being synced, avoiding unnecessary re-runs.


93-109: LGTM! Proper label associations for accessibility.

The toggle control is correctly associated with its labels via matching htmlFor and id attributes, meeting accessibility requirements.

__tests__/components/user/subscriptions/UserPageSubscriptionsMode.test.tsx (1)

27-28: LGTM! Test data updated to match generated model.

The addition of subscribe_all_editions and subscription_eligibility_count aligns the test fixture with the expanded SubscriptionDetails model structure.

components/user/subscriptions/UserPageSubscriptionsHistory.tsx (2)

230-231: LGTM! Proper null-safe date rendering.

The optional chaining correctly guards against missing timestamps before attempting to format them.


253-254: LGTM! Clear quantity display format.

The "x{count}" format effectively communicates the edition quantity received.

components/user/subscriptions/UserPageSubscriptionsBalance.tsx (1)

4-4: LGTM! Import updated to generated model.

This change aligns with the broader migration from local interfaces to generated models across subscription components.

components/subscriptions-report/SubscriptionsReport.tsx (1)

18-19: LGTM! Imports updated to generated models.

The migration to generated models for RedeemedSubscriptionCounts and SubscriptionCounts aligns with the broader refactoring across subscription-related components.

__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (2)

83-90: LGTM! Test fixture updated to match generated model.

The mock data structure correctly reflects all fields in the SubscriptionDetails model, with appropriate test values.


215-215: LGTM! Regex match improves test resilience.

Using a regex pattern instead of an exact string match makes the test more robust to text variations while still validating the key message.

components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (5)

80-87: Past issue resolved: Undefined rows[index] now handled correctly.

The previous concern about rows[index] potentially being undefined when there are fewer eligible mint days than subscriptions has been addressed. Line 87 now uses date={rows[index] ?? null} to safely handle this case, and the prop type at line 112 correctly declares date: SeasonMintRow | null.


124-139: Past issue resolved: Validation added when eligibilityCount decreases.

The previous concern about the count selector lacking validation when eligibilityCount decreases below selectedCount has been properly addressed. The useEffect at lines 135-139 now watches for changes to eligibilityCount and automatically clamps selectedCount down to the new limit (with a minimum of 1), ensuring the select element always has a valid matching option.


194-203: Past issue resolved: Redundant state setter removed from error handler.

The previous redundant setSubscribed(subscribed) call in the catch block has been correctly removed. The error handler now only displays the toast message and returns, which is the appropriate behavior since the subscribed state was never mutated during the failed async operation.


264-293: Past issue resolved: Null guards properly added for props.date access.

The previous concern about unguarded access to props.date.seasonIndex and props.date.utcDay has been correctly addressed. Line 264 now wraps the entire date/season display block in a conditional {props.date && ...}, ensuring no property access occurs when date is null. This prevents runtime errors when there are fewer eligible mint days than subscriptions.


318-352: Past issue resolved: Accessible label added to count selector.

The previous concern about the missing aria-label on the count selector has been addressed. Line 336 now includes aria-label="Select subscription quantity for ${props.title}", making the control's purpose clear to screen reader users. The implementation properly disables the selector when eligibilityCount <= 1 or during submission, and the options are correctly generated from the eligibility count.

Comment thread components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
components/user/layout/UserPageLayout.tsx (1)

17-18: Consider removing unnecessary optional chaining.

The initialProfile parameter is typed as non-nullable ApiIdentity, so the optional chaining operator (?.) on line 17 is unnecessary. For consistency with the type system, consider using direct property access:

🔎 Suggested refactor
  const mainAddress =
-    initialProfile?.primary_wallet ?? normalizedHandleOrWallet;
+    initialProfile.primary_wallet ?? normalizedHandleOrWallet;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c598f33 and b9ef283.

⛔ Files ignored due to path filters (1)
  • generated/models/GetRedeemedMemeSubscriptionCounts200Response.ts is excluded by !**/generated/**
📒 Files selected for processing (1)
  • components/user/layout/UserPageLayout.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/user/layout/UserPageLayout.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/layout/UserPageLayout.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/user/layout/UserPageLayout.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/user/layout/UserPageLayout.tsx
🧬 Code graph analysis (1)
components/user/layout/UserPageLayout.tsx (1)
components/user/layout/UserPageClientHydrator.tsx (1)
  • UserPageClientHydrator (18-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
components/user/layout/UserPageLayout.tsx (2)

21-21: LGTM! Flex layout structure is well-designed.

The addition of tw-flex tw-flex-col to the main container and tw-flex-1 to the content area creates a proper flexible column layout that allows the page content to expand appropriately.

Also applies to: 26-26


30-30: LGTM! Fallback address prop correctly wired.

The fallbackMainAddress prop provides a sensible fallback mechanism for the header component, using the primary wallet when available or falling back to the normalized handle/wallet.

Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (1)

136-140: Optimize the useEffect to avoid unnecessary re-runs.

Including selectedCount in the dependency array causes the effect to run on every selection change, even when no clamping is needed. Use a functional state update to eliminate this dependency:

🔎 Optimized implementation
 useEffect(() => {
-  if (selectedCount > props.eligibilityCount) {
-    setSelectedCount(Math.max(1, props.eligibilityCount));
-  }
-}, [props.eligibilityCount, selectedCount]);
+  setSelectedCount(prev => Math.min(prev, Math.max(1, props.eligibilityCount)));
+}, [props.eligibilityCount]);

This ensures selectedCount is clamped only when eligibilityCount changes, avoiding no-op effect runs during normal user interaction.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9ef283 and eaa1bfb.

⛔ Files ignored due to path filters (1)
  • generated/models/ObjectSerializer.ts is excluded by !**/generated/**
📒 Files selected for processing (2)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (3 hunks)
  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/{components,contexts,hooks}/**/*.{ts,tsx} : Use semantic HTML elements (`<label>`, `<output>`) over ARIA attributes when possible; every form control must have a label

Applied to files:

  • components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread components/user/subscriptions/UserPageSubscriptionsUpcoming.tsx
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (5)
__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx (2)

65-74: Verify cache invalidation behavior.

The test "toggles subscription and posts update" only verifies the API call but doesn't check whether cache invalidation occurs after the subscription toggle. Since useQueryClient was added to the mock, this behavior should be tested.

🔎 Add cache invalidation assertion

After capturing mockInvalidateQueries (see previous comment), add:

 test("toggles subscription and posts update", async () => {
   const user = userEvent.setup();
   (commonApiPost as jest.Mock).mockResolvedValue({
     token_id: 1,
     subscribed: false,
   });
   const { container } = renderComp();
   await user.click(screen.getByTestId("toggle"));
   expect(commonApiPost).toHaveBeenCalled();
+  expect(mockInvalidateQueries).toHaveBeenCalled();
 });

41-42: Replace as any with proper typing.

Using as any bypasses TypeScript's type checking and may hide incomplete mock data. Import the proper types and provide complete mock objects.

🔎 Proposed fix with proper types
+import { NFTSubscription } from "@/generated/models/NFTSubscription";
+import { SubscriptionDetails } from "@/generated/models/SubscriptionDetails";
+
-const sub = { token_id: 1, contract: "0x123", subscribed: true } as any;
-const details = { profile: "test" } as any;
+const sub: NFTSubscription = {
+  token_id: 1,
+  contract: "0x123",
+  subscribed: true,
+} as NFTSubscription;
+
+const details: SubscriptionDetails = {
+  consolidation_key: "test",
+  last_update: 1234567890,
+  balance: 1,
+  automatic: true,
+  subscribe_all_editions: false,
+  subscription_eligibility_count: 1,
+} as SubscriptionDetails;
__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (3)

9-10: Unused mock: add assertions or remove.

mockInvalidateQueries is captured and wired into the mock but never asserted in any test. This suggests incomplete test coverage for cache invalidation behavior or unnecessary mock setup.

Either add assertions to verify cache invalidation:

it("invalidates cache after subscription toggle", () => {
  // ... perform action that should invalidate
  expect(mockInvalidateQueries).toHaveBeenCalledWith(
    expect.arrayContaining([expect.stringContaining("subscription")])
  );
});

Or if cache invalidation is not relevant to these tests, remove the captured mock and use an inline jest.fn().

Also applies to: 13-15


65-86: Type assertions may hide incomplete mock data.

Using as NFTSubscription type assertions bypasses TypeScript's structural type checking. If the generated NFTSubscription model requires additional fields, these assertions will silently hide the missing data.

Consider satisfies operator (TypeScript 4.9+) or remove the assertion to let TypeScript verify completeness:

const mockSubscriptions: NFTSubscription[] = [
  {
    token_id: 1,
    contract: "0x123",
    subscribed: true,
  } satisfies NFTSubscription,
  // ...
];

This provides type safety while still allowing inference.


220-220: Regex may match unintended text.

Changing from exact string match to /Minting Today/ increases test flexibility but could match unintended text like "Not Minting Today" or "Minting Today: Postponed".

If the exact text is stable, consider a more precise assertion:

expect(screen.getByText("Minting Today")).toBeInTheDocument();

Or use a more specific regex if needed:

expect(screen.getByText(/^Minting Today$/)).toBeInTheDocument();
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eaa1bfb and 102e26a.

📒 Files selected for processing (2)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (3 hunks)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/@(__tests__|*.test).{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Tests should live in __tests__/ or ComponentName.test.tsx; mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Place tests in __tests__/ directory or as ComponentName.test.tsx alongside components

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use Jest + ts-jest for TypeScript testing

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Functions must have ≤ 15 cognitive complexity; extract deep ternaries (>3 levels) and break down complex logic

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (tests/AGENTS.md)

__tests__/**/*.{ts,tsx,js}: Prefer for...of loops over forEach as it allows break/continue and works with async/await
Use array.at(-1) and array.at(-2) instead of index-based array access for negative indexing
Use String.prototype.replaceAll() instead of replace() for global string replacements
Use globalThis.fetch() instead of direct fetch() calls
Organize imports with one import per module in order: external → internal → types, with no duplicates
Use element.remove() instead of parent.removeChild(element) for DOM manipulation
Catch errors only when meaningful; no empty catch blocks; log errors with context
Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (?.)

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
__tests__/**/{components,contexts,hooks}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use semantic HTML elements (<label>, <output>) over ARIA attributes when possible; every form control must have a label

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-05T10:55:30.871Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-05T10:55:30.871Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Use testing-library/react + user-event for React component tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Applies to **/@(__tests__|*.test).{ts,tsx} : Tests should live in `__tests__/` or `ComponentName.test.tsx`; mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-11-25T08:35:58.729Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.729Z
Learning: Applies to **/*.{tsx,jsx} : Use react-query for data fetching

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Test accessibility with keyboard navigation and screen reader compatibility

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)

Comment thread __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx Outdated
Signed-off-by: prxt6529 <prxt@6529.io>
Signed-off-by: prxt6529 <prxt@6529.io>
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (1)

9-16: Consider verifying cache invalidation behavior.

The mockInvalidateQueries mock is properly set up but never asserted in any test case. If UserPageSubscriptionsUpcoming is expected to invalidate queries (e.g., after subscription changes), consider adding test coverage to verify this behavior.

Example test case to verify invalidation
it("invalidates queries after subscription toggle", async () => {
  renderComponent();
  
  const toggleButton = screen.getAllByRole("button").find(btn => 
    btn.getAttribute("aria-label")?.includes("Toggle subscription")
  );
  
  if (toggleButton && !toggleButton.hasAttribute("disabled")) {
    fireEvent.click(toggleButton);
    await waitFor(() => {
      expect(mockInvalidateQueries).toHaveBeenCalled();
    });
  }
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 102e26a and 407cc60.

⛔ Files ignored due to path filters (1)
  • generated/models/ObjectSerializer.ts is excluded by !**/generated/**
📒 Files selected for processing (2)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (3 hunks)
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Do not include any comments in the code; it should be self-explanatory
Write correct, up-to-date, bug-free, fully componentized, secure, and efficient code
Include all required imports and ensure proper naming of key components
Use NextJS features that match the current version

**/*.{ts,tsx,js,jsx}: Replace <img> elements with <Image /> from next/image to satisfy @next/next/no-img-element ESLint rule
Use <Link href="/path"> from Next.js for internal navigation instead of plain HTML links to satisfy @next/next/no-html-link-for-pages ESLint rule

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,jsx}: Use FontAwesome for icons in React components
Use TailwindCSS for styling in React components
Use react-query for data fetching
Always add readonly before props in React components

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks)
Use framework APIs: internal links should use <Link>, images should use next/image, and adopt Next's ESLint rules (Core Web Vitals)

**/*.{js,jsx,ts,tsx}: Code must satisfy ESLint (Next's Core Web Vitals + React Hooks rules)
Follow existing code style and naming conventions; maintain clean code standards (measured by SonarQube)

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Must pass tsc --noEmit type checking
Prefer direct named imports for React hooks and types (import { useMemo, useRef, FC, etc. } from "react") over React. namespace usage (React.useMemo, React.useRef, etc.)
If the react-hooks/exhaustive-deps lint rule is triggered: if the Effect only derives state, remove the Effect and compute during render; if listening to an external system and needing fresh props/state, wrap non-reactive logic in useEffectEvent

**/*.{ts,tsx}: Must pass tsc --noEmit for TypeScript type checking
Prefer Server Components over Client Components; use Server Functions/Server Actions ('use server') for mutations
Remove unnecessary Effects; if Effect only derives state, compute during render instead
Use useEffectEvent for non-reactive logic inside Effects to avoid unnecessary re-runs
Use framework APIs: <Link> for internal links, next/image for images, adopt Next's ESLint rules
Use 'use cache' directive and Cache Components features for explicit opt-in caching in Next.js 16
Use TypeScript and React functional components with hooks
When parsing Seize URLs or similar, fail fast if base origin is unavailable; do not fall back to placeholder origins
Replace <img> elements with <Image /> from next/image
Use <Link href="/path"> for internal navigation instead of plain HTML links
Move data fetches to Server Components; handle mutations through Server Functions/Server Actions with 'use server' directive

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
**/@(__tests__|*.test).{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Tests should live in __tests__/ or ComponentName.test.tsx; mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Place tests in __tests__/ directory or as ComponentName.test.tsx alongside components

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Mock external dependencies and APIs in tests

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use Jest + ts-jest for TypeScript testing

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Functions must have ≤ 15 cognitive complexity; extract deep ternaries (>3 levels) and break down complex logic

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
__tests__/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (tests/AGENTS.md)

__tests__/**/*.{ts,tsx,js}: Prefer for...of loops over forEach as it allows break/continue and works with async/await
Use array.at(-1) and array.at(-2) instead of index-based array access for negative indexing
Use String.prototype.replaceAll() instead of replace() for global string replacements
Use globalThis.fetch() instead of direct fetch() calls
Organize imports with one import per module in order: external → internal → types, with no duplicates
Use element.remove() instead of parent.removeChild(element) for DOM manipulation
Catch errors only when meaningful; no empty catch blocks; log errors with context
Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (?.)

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
__tests__/**/{components,contexts,hooks}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (tests/AGENTS.md)

Use semantic HTML elements (<label>, <output>) over ARIA attributes when possible; every form control must have a label

Files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
🧠 Learnings (9)
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Applies to **/@(__tests__|*.test).{ts,tsx} : Tests should live in `__tests__/` or `ComponentName.test.tsx`; mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/*.test.{ts,tsx,js} : Keep tests independent, deterministic, and fast with production-like data

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:30.871Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-05T10:55:30.871Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies and APIs in tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/*.{ts,tsx,js} : Avoid double negatives in code; prefer explicit logic and remove redundant annotations; use optional chaining (`?.`)

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Test accessibility with keyboard navigation and screen reader compatibility

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx
📚 Learning: 2025-12-05T10:55:43.476Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __tests__/AGENTS.md:0-0
Timestamp: 2025-12-05T10:55:43.476Z
Learning: Applies to __tests__/**/__tests__/**/components/**/*.test.{ts,tsx} : Use testing-library/react + user-event for React component tests

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-11-25T08:35:58.729Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T08:35:58.729Z
Learning: Applies to **/*.{tsx,jsx} : Use react-query for data fetching

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-12-03T14:52:34.271Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-03T14:52:34.271Z
Learning: Fix with modernization (no `// eslint-disable` unless explicitly instructed); prefer refactors aligned with React 19.2, React Compiler, and Next.js 16 conventions

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
📚 Learning: 2025-11-25T08:37:21.058Z
Learnt from: CR
Repo: 6529-Collections/6529seize-frontend PR: 0
File: __mocks__/AGENTS.md:0-0
Timestamp: 2025-11-25T08:37:21.058Z
Learning: Applies to __mocks__/**/__mocks__/** : Document expected behaviour in the mock file when it is not obvious

Applied to files:

  • __tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx
🧬 Code graph analysis (1)
__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (1)
generated/models/SubscriptionDetails.ts (1)
  • SubscriptionDetails (15-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.test.tsx (3)

4-5: LGTM! Migration to generated models completed.

The imports now use generated model types, aligning with the PR's objective to replace legacy subscription types with generated models.


88-97: Excellent fix for test determinism!

The introduction of FIXED_TIMESTAMP resolves the previous review concern about non-deterministic test data from Date.now(). The mock data now includes all required fields from the generated SubscriptionDetails model with stable, predictable values.


222-222: Good use of regex for flexible text matching.

Using /Minting Today/ instead of an exact string match makes the assertion more resilient to minor text variations (e.g., punctuation changes or surrounding text).

__tests__/components/user/subscriptions/UserPageSubscriptionsUpcoming.toggle.test.tsx (4)

12-17: Previous review feedback addressed successfully.

The mock is now properly captured as mockInvalidateQueries, enabling assertions on cache invalidation behavior. This resolves the inconsistency noted in the previous review.


48-50: Good addition for test isolation.

Adding beforeEach to clear mocks ensures each test runs with a clean slate and prevents false positives from accumulated mock call history.


77-77: Clean refactor removing unused return value.

Since the test uses screen queries and doesn't reference container, removing the capture is appropriate.


80-84: Assertion correctly verifies cache invalidation.

The assertion structure properly validates that subscription-related queries are invalidated after toggling. The flexible matching strategy using stringContaining is appropriate and doesn't over-couple the test to implementation details.

Signed-off-by: prxt6529 <prxt@6529.io>
@sonarqubecloud
Copy link
Copy Markdown

@prxt6529 prxt6529 merged commit d8e1063 into main Dec 19, 2025
9 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Feb 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants