refactor: convert forwardRef components to function components for consistency

This commit is contained in:
Dries Augustyns
2026-05-04 21:08:14 +02:00
parent ed9027b4ef
commit 361ec0b1eb
35 changed files with 869 additions and 889 deletions
+1 -1
View File
@@ -241,7 +241,7 @@ export function ActivityFeed({typeFilter, dateRangeDays = 30, contactId}: Activi
<div className="space-y-4">
{upcomingActivities.map((activity, index) => (
<div key={`${activity.id}-${index}`}>
<ActivityItem activity={activity} isUpcoming={true} />
<ActivityItem activity={activity} status="upcoming" />
{index < upcomingActivities.length - 1 && <div className="border-t border-neutral-100 my-4" />}
</div>
))}
+3 -2
View File
@@ -110,7 +110,7 @@ function isEmailActivity(type: string): boolean {
interface ActivityItemProps {
activity: Activity;
isUpcoming?: boolean;
status?: 'upcoming' | 'completed';
}
interface ActivityConfig {
@@ -342,11 +342,12 @@ function getActivityConfig(activity: Activity): ActivityConfig {
}
}
export const ActivityItem = memo(function ActivityItem({activity, isUpcoming = false}: ActivityItemProps) {
export const ActivityItem = memo(function ActivityItem({activity, status = 'completed'}: ActivityItemProps) {
const [showPreviewModal, setShowPreviewModal] = useState(false);
const config = getActivityConfig(activity);
const Icon = config.icon;
const timestamp = new Date(activity.timestamp);
const isUpcoming = status === 'upcoming';
const relativeTime = isUpcoming ? getUpcomingTime(timestamp) : getRelativeTime(timestamp);
return (
+1 -3
View File
@@ -9,7 +9,6 @@ interface ApiKeyDisplayProps {
description?: string;
isSecret?: boolean;
onRegenerate?: () => Promise<void>;
showRegenerate?: boolean;
}
export function ApiKeyDisplay({
@@ -18,7 +17,6 @@ export function ApiKeyDisplay({
description,
isSecret = false,
onRegenerate,
showRegenerate = false,
}: ApiKeyDisplayProps) {
const [showKey, setShowKey] = useState(!isSecret);
const [copied, setCopied] = useState(false);
@@ -101,7 +99,7 @@ export function ApiKeyDisplay({
)}
</AnimatePresence>
</Button>
{showRegenerate && onRegenerate && (
{onRegenerate && (
<Button
type="button"
variant="outline"
+6 -6
View File
@@ -59,13 +59,13 @@ const formatEmailCost = (emailCount: number, currency: string | null): string =>
interface BillingLimitsProps {
projectId: string;
hasSubscription: boolean;
tier: 'free' | 'paid';
billingEnabled: boolean;
}
type LimitsFormValues = z.infer<typeof BillingLimitSchemas.update>;
export function BillingLimits({projectId, hasSubscription, billingEnabled}: BillingLimitsProps) {
export function BillingLimits({projectId, tier, billingEnabled}: BillingLimitsProps) {
const [isEditing, setIsEditing] = useState(false);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
@@ -132,7 +132,7 @@ export function BillingLimits({projectId, hasSubscription, billingEnabled}: Bill
};
// Free tier projects can view their usage but can't edit limits
const canEditLimits = hasSubscription;
const canEditLimits = tier === 'paid';
// If billing is not enabled, don't show the component
if (!billingEnabled) {
@@ -160,7 +160,7 @@ export function BillingLimits({projectId, hasSubscription, billingEnabled}: Bill
<CardHeader>
<CardTitle>Billing Limits</CardTitle>
<CardDescription>
{hasSubscription
{tier === 'paid'
? 'Set monthly limits for each email category. Limits reset on the 1st of each month.'
: 'Free tier projects have a total limit of 1,000 emails per month across all categories.'}
</CardDescription>
@@ -168,7 +168,7 @@ export function BillingLimits({projectId, hasSubscription, billingEnabled}: Bill
<CardContent>
<div className="space-y-6">
{/* Free tier info banner */}
{!hasSubscription && limitsData && (
{tier !== 'paid' && limitsData && (
<Alert>
<AlertCircle className="h-4 w-4" />
<div className="ml-2">
@@ -208,7 +208,7 @@ export function BillingLimits({projectId, hasSubscription, billingEnabled}: Bill
{!isEditing && limitsData && (
<div className="space-y-4">
{/* For free tier, show total usage across all categories */}
{!hasSubscription ? (
{tier !== 'paid' ? (
<UsageDisplay
category="Total Emails (All Categories)"
usage={limitsData.workflows}
@@ -76,7 +76,6 @@ export function EmailEditor({value, onChange, placeholder, subject, from, replyT
// Fetch contacts for preview using SWR
const {contacts} = useContacts({limit: 50});
// Update available variables when fields change
useEffect(() => {
if (availableFields.length > 0) {
setAvailableVariables(availableFields);
+3 -11
View File
@@ -11,14 +11,9 @@ interface EmailSettingsProps {
fromPlaceholder?: string;
fromNamePlaceholder?: string;
replyToPlaceholder?: string;
showFromNameHelpText?: boolean;
layout?: 'vertical' | 'grid';
}
/**
* Reusable email settings component for from, fromName, and replyTo fields
* Used in campaign and template forms
*/
export function EmailSettings({
from,
fromName,
@@ -29,7 +24,6 @@ export function EmailSettings({
fromPlaceholder = 'hello',
fromNamePlaceholder = 'Your Company',
replyToPlaceholder,
showFromNameHelpText = false,
layout = 'grid',
}: EmailSettingsProps) {
// Use from email's local part as the reply-to placeholder if not provided
@@ -63,11 +57,9 @@ export function EmailSettings({
onChange={e => onFromNameChange(e.target.value)}
placeholder={fromNamePlaceholder}
/>
{showFromNameHelpText && (
<p className="text-xs text-neutral-500 mt-1">
The sender name that appears in the recipient&apos;s inbox. Defaults to your project name if not set.
</p>
)}
<p className="text-xs text-neutral-500 mt-1">
The sender name that appears in the recipient&apos;s inbox. Defaults to your project name if not set.
</p>
</div>
</GridWrapper>