diff --git a/apps/web/src/components/ActivityItem.tsx b/apps/web/src/components/ActivityItem.tsx
index 4608eb8..cf32c14 100644
--- a/apps/web/src/components/ActivityItem.tsx
+++ b/apps/web/src/components/ActivityItem.tsx
@@ -1,5 +1,6 @@
import {Badge, Collapsible, CollapsibleContent, CollapsibleTrigger} from '@plunk/ui';
import type {Activity} from './ActivityFeed';
+import {memo} from 'react';
import {
AlertCircle,
Calendar,
@@ -293,7 +294,7 @@ function getActivityConfig(activity: Activity): ActivityConfig {
}
}
-export function ActivityItem({activity, isUpcoming = false}: ActivityItemProps) {
+export const ActivityItem = memo(function ActivityItem({activity, isUpcoming = false}: ActivityItemProps) {
const config = getActivityConfig(activity);
const Icon = config.icon;
const timestamp = new Date(activity.timestamp);
@@ -358,4 +359,4 @@ export function ActivityItem({activity, isUpcoming = false}: ActivityItemProps)
);
-}
+});
diff --git a/apps/web/src/components/BillingConsumption.tsx b/apps/web/src/components/BillingConsumption.tsx
index fe58cfb..3c23eca 100644
--- a/apps/web/src/components/BillingConsumption.tsx
+++ b/apps/web/src/components/BillingConsumption.tsx
@@ -2,6 +2,7 @@ import {Card, CardContent, CardDescription, CardHeader, CardTitle, Alert} from '
import {AlertCircle, TrendingUp} from 'lucide-react';
import {useBillingConsumption} from '../lib/hooks/useBillingConsumption';
import {useConfig} from '../lib/hooks/useConfig';
+import {useCallback} from 'react';
interface BillingConsumptionProps {
projectId: string;
@@ -15,6 +16,22 @@ export function BillingConsumption({projectId, hasSubscription}: BillingConsumpt
// Always call the hook to satisfy Rules of Hooks
const {consumptionData, isLoading, error} = useBillingConsumption(projectId, hasSubscription && billingEnabled);
+ // Define callbacks BEFORE any conditional returns (Rules of Hooks)
+ const formatCurrency = useCallback((amount: number, currency: string) => {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: currency.toUpperCase(),
+ }).format(amount / 100);
+ }, []);
+
+ const formatDate = useCallback((dateString: string) => {
+ return new Date(dateString).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ });
+ }, []);
+
if (!billingEnabled) {
// If billing is globally disabled, hide the card entirely
return null;
@@ -79,21 +96,6 @@ export function BillingConsumption({projectId, hasSubscription}: BillingConsumpt
return null;
}
- const formatCurrency = (amount: number, currency: string) => {
- return new Intl.NumberFormat('en-US', {
- style: 'currency',
- currency: currency.toUpperCase(),
- }).format(amount / 100);
- };
-
- const formatDate = (dateString: string) => {
- return new Date(dateString).toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- });
- };
-
return (