Files
supabase/apps/studio/components/interfaces/Home/ProjectList/ProjectCardStatus.tsx
T
Jordi EnricandClaude Sonnet 4.6 9c5fabcc7b fix(project-list): remove duplicate resource exhaustion badge DEBUG-48 (#44174)
## Problem

Project cards on the home page showed resource exhaustion warnings
twice: once as a badge in the card header, and again as an alert in the
card footer. This created a confusing, redundant UI as seen when a
project has disk IO, CPU, or other resource warnings active.

## Fix

Removed `resourceWarnings` from the footer `ProjectCardStatus` render in
`ProjectCard.tsx`. Resource exhaustion state now only appears as the
header badge (which already shows a tooltip with the description on
hover). Project status indicators such as paused or restarting still
appear in the footer since those do not depend on resource warnings.

Also removed the dead `showResourceExhaustionWarnings = false` flag from
`ProjectCardStatus.tsx`, which was hardcoded to false and only gated
description text that was no longer reachable.

## How to test

- Navigate to the org home page with a project that has an active
resource warning (disk IO, disk space, CPU, memory, or read-only mode).
- Confirm the warning badge appears in the card header with the correct
color (warning or critical).
- Confirm no duplicate alert appears in the card footer area.
- Confirm that paused or restarting projects still show the status alert
in the footer.
- Confirm the table view still shows the badge correctly in the status
column.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

**Style**
- Project status information now displays exclusively in the project
card footer, removing redundant header display for a cleaner interface.
- Simplified resource warning notification logic for more consistent and
straightforward warning presentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 17:01:04 +02:00

202 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { AlertTriangle, Info, PauseCircle, RefreshCcw } from 'lucide-react'
import { Badge, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { InferredProjectStatus } from './ProjectCard.utils'
import { RESOURCE_WARNING_MESSAGES } from '@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.constants'
import { getWarningContent } from '@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner.utils'
import type { ResourceWarning } from '@/data/usage/resource-warnings-query'
export interface ProjectCardWarningsProps {
resourceWarnings?: ResourceWarning
projectStatus: InferredProjectStatus
renderMode?: 'alert' | 'badge'
}
export const ProjectCardStatus = ({
resourceWarnings: allResourceWarnings,
projectStatus,
renderMode = 'alert',
}: ProjectCardWarningsProps) => {
// [Terry] temp to remove auth_restricted_email_sending property from resourceWarnings
// set auth_restricted_email_sending from 'warning' to null so it doesn't show up in the warning banner
// [Joshen] Can remove this eventually once the auth email thing is resolved (Nov 2024)
const resourceWarnings = allResourceWarnings
? {
...allResourceWarnings,
auth_restricted_email_sending: null,
}
: undefined
// [Joshen] Read only takes higher precedence over multiple resource warnings
const activeWarnings = resourceWarnings?.is_readonly_mode_enabled
? ['is_readonly_mode_enabled']
: Object.keys(resourceWarnings || {}).filter(
(property) =>
property !== 'project' &&
property !== 'is_readonly_mode_enabled' &&
resourceWarnings?.[property as keyof typeof resourceWarnings] !== null
)
const hasCriticalWarning = activeWarnings.some(
(x) => resourceWarnings?.[x as keyof typeof resourceWarnings] === 'critical'
)
const isCritical = activeWarnings.includes('is_readonly_mode_enabled') || hasCriticalWarning
const warningContent =
resourceWarnings !== undefined
? getWarningContent(resourceWarnings, activeWarnings[0], 'cardContent')
: undefined
const getTitle = () => {
switch (projectStatus) {
case 'isPaused':
return renderMode === 'badge' ? 'Paused' : 'Project is paused'
case 'isPausing':
return renderMode === 'badge' ? 'Pausing' : 'Project is pausing'
case 'isRestarting':
return renderMode === 'badge' ? 'Restarting' : 'Project is restarting'
case 'isResizing':
return renderMode === 'badge' ? 'Resizing' : 'Project is resizing'
case 'isComingUp':
return renderMode === 'badge' ? 'Starting' : 'Project is coming up'
case 'isRestoring':
return renderMode === 'badge' ? 'Restoring' : 'Project is restoring'
case 'isUpgrading':
return renderMode === 'badge' ? 'Upgrading' : 'Project is upgrading'
case 'isRestoreFailed':
return renderMode === 'badge' ? 'Restore Failed' : 'Project restore failed'
case 'isPauseFailed':
return renderMode === 'badge' ? 'Pause Failed' : 'Project pause failed'
}
if (!resourceWarnings) return undefined
// If none of the paused/restoring states match, proceed with the default logic
return activeWarnings.length > 1
? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.cardContent[
hasCriticalWarning ? 'critical' : 'warning'
].title
: warningContent?.title
}
const getDescription = () => {
switch (projectStatus) {
case 'isPaused':
return 'This project will not accept requests until resumed'
case 'isPausing':
return 'The pause process will complete in a few minutes'
case 'isRestarting':
case 'isResizing':
case 'isComingUp':
case 'isRestoring':
case 'isUpgrading':
return 'Your project will be ready in a few minutes'
case 'isRestoreFailed':
case 'isPauseFailed':
return 'Please contact support for assistance'
}
if (!resourceWarnings) return undefined
// If none of the paused/restoring states match, proceed with the default logic
return activeWarnings.length > 1
? RESOURCE_WARNING_MESSAGES.multiple_resource_warnings.cardContent[
hasCriticalWarning ? 'critical' : 'warning'
].description
: warningContent?.description
}
const alertTitle = getTitle()
const alertDescription = getDescription()
const alertType = isCritical
? 'destructive'
: projectStatus === 'isPaused'
? 'default'
: 'warning'
if (
(activeWarnings.length === 0 || warningContent === undefined) &&
projectStatus === 'isHealthy'
) {
if (renderMode === 'badge') {
return (
// Badge must be wrapped in a div in order to be centered in table cell
<div className="flex items-center">
<Badge variant="success">Active</Badge>
</div>
)
}
return null
}
if (renderMode === 'badge') {
// Render a fallback en dash if no title is available
if (!alertTitle) return <span className="text-xs text-foreground-muted">–</span>
const badgeVariant = isCritical
? 'destructive'
: activeWarnings.length > 0 ||
projectStatus === 'isPauseFailed' ||
projectStatus === 'isRestoreFailed'
? 'warning'
: projectStatus === 'isHealthy'
? 'success'
: 'default'
return (
// Badge must be wrapped in a div in order to be centered in table cell
<div className="flex items-center">
{alertDescription ? (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant={badgeVariant}>{alertTitle}</Badge>
</TooltipTrigger>
<TooltipContent side="bottom">{alertDescription}</TooltipContent>
</Tooltip>
) : (
<Badge variant={badgeVariant}>{alertTitle}</Badge>
)}
</div>
)
}
// Only render if an alert title is available
if (!alertTitle) return null
return (
<div role="alert" className={cn('w-full p-5 pb-[1.25rem] flex flex-row gap-x-2 items-center')}>
{/* Icon */}
<div
className={cn(
'shrink-0 w-6 h-6 border rounded-md flex items-center justify-center',
alertType === 'destructive' && 'border-destructive-400 [&>svg]:text-destructive-600',
alertType === 'warning' && 'border-warning-400 [&>svg]:text-warning-600',
alertType === 'default' && 'border-strong [&>svg]:text-foreground'
)}
>
{['isPaused', 'isPausing'].includes(projectStatus ?? '') ? (
<PauseCircle strokeWidth={1.5} size={14} />
) : ['isRestoring', 'isComingUp', 'isRestarting', 'isResizing'].includes(
projectStatus ?? ''
) ? (
<RefreshCcw strokeWidth={1.5} size={14} />
) : (
<AlertTriangle strokeWidth={1.5} size={14} />
)}
</div>
{/* Text and tooltip icon */}
<div className="flex items-center w-full gap-x-2">
<p className="text-xs">{alertTitle}</p>
<Tooltip>
<TooltipTrigger>
<Info
size={12}
className="text-foreground-lighter hover:text-foreground transition-colors"
/>
</TooltipTrigger>
<TooltipContent side="bottom">{alertDescription}</TooltipContent>
</Tooltip>
</div>
</div>
)
}