implement the home page#3
Conversation
Summary by CodeRabbit
WalkthroughThe PR replaces the placeholder home tab screen with a subscriptions dashboard. It introduces global type interfaces, a ChangesSubscriptions Home Screen
Sequence Diagram(s)sequenceDiagram
participant User
participant HomeScreen as app/(tabs)/index.tsx
participant SubscriptionCard
participant Utils as lib/utils.ts
User->>HomeScreen: View home tab
HomeScreen->>Utils: formatCurrency(HOME_BALANCE.amount)
Utils-->>HomeScreen: formatted balance string
HomeScreen->>HomeScreen: render horizontal FlatList(UPCOMING_SUBSCRIPTIONS)
HomeScreen->>HomeScreen: render vertical FlatList(HOME_SUBSCRIPTIONS)
User->>SubscriptionCard: tap card
SubscriptionCard->>HomeScreen: onPress(item.id)
HomeScreen->>HomeScreen: setExpandedSubscriptionId(toggle id)
HomeScreen->>SubscriptionCard: re-render with expanded=true
SubscriptionCard->>Utils: formatSubscriptionDateTime(startDate/renewalDate)
SubscriptionCard->>Utils: formatStatusLabel(status)
Utils-->>SubscriptionCard: formatted strings
SubscriptionCard-->>User: display expanded details
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/`(tabs)/index.tsx:
- Around line 56-57: The ListHeading component for both the "Upcoming" section
and the other section (around line 72) has a visible "View All" CTA but no
navigation handler is connected to it. Add an onPress or onViewAllPress prop to
each ListHeading component that navigates to the appropriate detail screen
showing all items for that section, ensuring the tap affordance now has a
functional outcome that displays the full list of upcoming or relevant items.
- Around line 29-74: The ListHeaderComponent is defined inline as an arrow
function, which causes the entire header subtree (including the horizontal
FlatList for upcoming subscriptions) to remount unnecessarily whenever the
parent component re-renders, especially when expandedSubscriptionId changes.
Extract the ListHeaderComponent into a separate memoized component (either as a
standalone function component wrapped with React.memo or extracted to a separate
variable wrapped with useMemo) and reference it in the FlatList props instead of
defining it inline. This will preserve the header state and prevent unnecessary
remounts of child components.
In `@components/ListHeading.tsx`:
- Around line 7-9: The TouchableOpacity component with className "list-action"
is rendered as an interactive element but lacks an onPress handler, making the
"View All" button non-functional. Add an onPress prop to the TouchableOpacity
component that defines what action should occur when the user taps the button,
such as navigating to a full list view or expanding content. Ensure the handler
is properly defined and passed to make this CTA interactive and functional in
the dashboard flow.
In `@components/SubscriptionCard.tsx`:
- Line 109: The ternary operator on line 109 in the SubscriptionCard component
is preventing the formatStatusLabel function from handling undefined status
values with its built-in "Unknown" fallback. Remove the conditional ternary
operator and instead call formatStatusLabel directly with the status parameter,
allowing the function to handle missing or falsy values appropriately without
rendering an empty string.
In `@constants/data.ts`:
- Around line 29-32: The HOME_BALANCE constant in constants/data.ts has a
nextRenewalDate property set to March 18, 2026, which is in the past relative to
the current reference date of June 23, 2026. Update the nextRenewalDate value in
the HOME_BALANCE object to a date that is after June 23, 2026 to ensure the
"next renewal" label displays correctly in the home header.
- Around line 25-27: The HOME_USER constant in the file has a typo in the name
property where "Recurrly" should be corrected to "Recurly" (with a single 'r').
Locate the HOME_USER object and update the name property value to use the
correct spelling of Recurly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 97027c3a-b62a-4307-a759-f1536d2e6acd
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
app/(tabs)/index.tsxcomponents/ListHeading.tsxcomponents/SubscriptionCard.tsxcomponents/UpcomingSubscriptionCard.tsxconstants/data.tsconstants/images.tslib/utils.tspackage.jsontype.d.ts
| ListHeaderComponent={() => ( | ||
| <> | ||
| <View className="home-header"> | ||
| <View className="home-user"> | ||
| <Image | ||
| source={images.avatar} | ||
| resizeMode="contain" | ||
| className="home-avatar" | ||
| /> | ||
| <Text className="home-user-name">{HOME_USER.name}</Text> | ||
| </View> | ||
| <Image source={icons.add} className="home-add-icon" /> | ||
| </View> | ||
|
|
||
| <View className="home-balance-card"> | ||
| <Text className="home-balance-label">Balance</Text> | ||
| <View className="home-balance-row"> | ||
| <Text className="home-balance-amount"> | ||
| {formatCurrency(HOME_BALANCE.amount)} | ||
| </Text> | ||
| <Text className="home-balance-date"> | ||
| {dayjs(HOME_BALANCE.nextRenewalDate).format("MM/DD")} | ||
| </Text> | ||
| </View> | ||
| </View> | ||
|
|
||
| <View className="mb-5"> | ||
| <ListHeading title="Upcoming" /> | ||
| <FlatList | ||
| horizontal | ||
| showsHorizontalScrollIndicator={false} | ||
| data={UPCOMING_SUBSCRIPTIONS} | ||
| renderItem={({ item }) => ( | ||
| <UpcomingSubscriptionCard {...item} /> | ||
| )} | ||
| keyExtractor={(item) => item.id} | ||
| ListEmptyComponent={ | ||
| <Text className="home-empty-state"> | ||
| No upcoming renewals yet. | ||
| </Text> | ||
| } | ||
| /> | ||
| </View> | ||
| <ListHeading title="All Subscriptions" /> | ||
| </> | ||
| )} |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Stabilize ListHeaderComponent to avoid unnecessary header subtree remounts.
Because the header is created inline, toggling expandedSubscriptionId recreates the entire header subtree (including the horizontal list). Extract/memoize the header component to preserve state and reduce render churn.
♻️ Suggested refactor
-import { useState } from "react";
+import { useMemo, useState } from "react";
@@
export default function App() {
const [expandedSubscriptionId, setExpandedSubscriptionId] = useState<
string | null
>(null);
+
+ const listHeader = useMemo(
+ () => (
+ <>
+ <View className="home-header">
+ <View className="home-user">
+ <Image source={images.avatar} resizeMode="contain" className="home-avatar" />
+ <Text className="home-user-name">{HOME_USER.name}</Text>
+ </View>
+ <Image source={icons.add} className="home-add-icon" />
+ </View>
+
+ <View className="home-balance-card">
+ <Text className="home-balance-label">Balance</Text>
+ <View className="home-balance-row">
+ <Text className="home-balance-amount">
+ {formatCurrency(HOME_BALANCE.amount)}
+ </Text>
+ <Text className="home-balance-date">
+ {dayjs(HOME_BALANCE.nextRenewalDate).format("MM/DD")}
+ </Text>
+ </View>
+ </View>
+
+ <View className="mb-5">
+ <ListHeading title="Upcoming" />
+ <FlatList
+ horizontal
+ showsHorizontalScrollIndicator={false}
+ data={UPCOMING_SUBSCRIPTIONS}
+ renderItem={({ item }) => <UpcomingSubscriptionCard {...item} />}
+ keyExtractor={(item) => item.id}
+ ListEmptyComponent={
+ <Text className="home-empty-state">No upcoming renewals yet.</Text>
+ }
+ />
+ </View>
+ <ListHeading title="All Subscriptions" />
+ </>
+ ),
+ [],
+ );
@@
- <FlatList
- ListHeaderComponent={() => (
- <>
- ...
- </>
- )}
+ <FlatList
+ ListHeaderComponent={listHeader}
data={HOME_SUBSCRIPTIONS}Also applies to: 88-88
🧰 Tools
🪛 React Doctor (0.5.8)
[warning] 30-30: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 31-31: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 32-32: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 33-33: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 38-38: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 40-40: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 43-43: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 44-44: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 45-45: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 46-46: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 49-49: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 55-55: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 56-56: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 57-57: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 62-62: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 66-66: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 72-72: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/`(tabs)/index.tsx around lines 29 - 74, The ListHeaderComponent is
defined inline as an arrow function, which causes the entire header subtree
(including the horizontal FlatList for upcoming subscriptions) to remount
unnecessarily whenever the parent component re-renders, especially when
expandedSubscriptionId changes. Extract the ListHeaderComponent into a separate
memoized component (either as a standalone function component wrapped with
React.memo or extracted to a separate variable wrapped with useMemo) and
reference it in the FlatList props instead of defining it inline. This will
preserve the header state and prevent unnecessary remounts of child components.
| <ListHeading title="Upcoming" /> | ||
| <FlatList |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
“View All” is a dead action in this screen flow.
This screen renders ListHeading with a visible CTA, but no actionable path is wired for either section, so users get a tap affordance with no outcome.
Also applies to: 72-72
🧰 Tools
🪛 React Doctor (0.5.8)
[warning] 56-56: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 57-57: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/`(tabs)/index.tsx around lines 56 - 57, The ListHeading component for
both the "Upcoming" section and the other section (around line 72) has a visible
"View All" CTA but no navigation handler is connected to it. Add an onPress or
onViewAllPress prop to each ListHeading component that navigates to the
appropriate detail screen showing all items for that section, ensuring the tap
affordance now has a functional outcome that displays the full list of upcoming
or relevant items.
| <TouchableOpacity className="list-action"> | ||
| <Text className="list-action-text">View All</Text> | ||
| </TouchableOpacity> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Dead CTA on Line 7 (“View All” is pressable but has no action).
TouchableOpacity is rendered as an interactive affordance, but no onPress is provided. This creates a non-functional action in the primary dashboard flow.
Proposed fix
-const ListHeading = ({ title }: ListHeadingProps) => {
+const ListHeading = ({ title, onViewAllPress }: ListHeadingProps) => {
return (
<View className="list-head">
<Text className="list-title">{title}</Text>
- <TouchableOpacity className="list-action">
+ <TouchableOpacity
+ className="list-action"
+ onPress={onViewAllPress}
+ disabled={!onViewAllPress}
+ accessibilityRole="button"
+ accessibilityState={{ disabled: !onViewAllPress }}
+ >
<Text className="list-action-text">View All</Text>
</TouchableOpacity>
</View>
);
}; interface ListHeadingProps {
title: string;
+ onViewAllPress?: () => void;
}🧰 Tools
🪛 React Doctor (0.5.8)
[warning] 7-7: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
[warning] 8-8: This JSX crashes because React isn't in scope.
If you're on React 17+ with the new JSX transform, disable this rule. Otherwise import React at the top of the file.
(react-in-jsx-scope)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/ListHeading.tsx` around lines 7 - 9, The TouchableOpacity
component with className "list-action" is rendered as an interactive element but
lacks an onPress handler, making the "View All" button non-functional. Add an
onPress prop to the TouchableOpacity component that defines what action should
occur when the user taps the button, such as navigating to a full list view or
expanding content. Ensure the handler is properly defined and passed to make
this CTA interactive and functional in the dashboard flow.
| numberOfLines={1} | ||
| ellipsizeMode="tail" | ||
| > | ||
| {status ? formatStatusLabel(status) : ""} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Line 109 bypasses the “Unknown” fallback from formatStatusLabel.
Current logic renders an empty string when status is missing, even though formatStatusLabel already handles undefined safely.
Proposed fix
- {status ? formatStatusLabel(status) : ""}
+ {formatStatusLabel(status)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {status ? formatStatusLabel(status) : ""} | |
| {formatStatusLabel(status)} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/SubscriptionCard.tsx` at line 109, The ternary operator on line
109 in the SubscriptionCard component is preventing the formatStatusLabel
function from handling undefined status values with its built-in "Unknown"
fallback. Remove the conditional ternary operator and instead call
formatStatusLabel directly with the status parameter, allowing the function to
handle missing or falsy values appropriately without rendering an empty string.
| export const HOME_USER = { | ||
| name: "S.ai | Recurrly", | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fix user-facing typo in display name.
"S.ai | Recurrly" looks like a typo (“Recurly”), and this string is rendered in the home header.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@constants/data.ts` around lines 25 - 27, The HOME_USER constant in the file
has a typo in the name property where "Recurrly" should be corrected to
"Recurly" (with a single 'r'). Locate the HOME_USER object and update the name
property value to use the correct spelling of Recurly.
| export const HOME_BALANCE = { | ||
| amount: 2489.48, | ||
| nextRenewalDate: "2026-03-18T09:00:00.000Z", | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Update fixture nextRenewalDate to a future date.
HOME_BALANCE.nextRenewalDate is set to March 18, 2026, which is already in the past relative to June 23, 2026. This makes the “next renewal” label incorrect in the home header.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@constants/data.ts` around lines 29 - 32, The HOME_BALANCE constant in
constants/data.ts has a nextRenewalDate property set to March 18, 2026, which is
in the past relative to the current reference date of June 23, 2026. Update the
nextRenewalDate value in the HOME_BALANCE object to a date that is after June
23, 2026 to ensure the "next renewal" label displays correctly in the home
header.
No description provided.