Redesign ServiceCard: card body is informational, links in footer

The card body no longer wraps in an <a> tag. Instead, the footer has
explicit icon buttons: external-link for the web URL, settings gear for
Coolify management, with a divider before start/stop/restart controls.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-04 00:52:50 +01:00
parent 5fd10819e4
commit 1a5be27a17

View File

@@ -101,34 +101,17 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
return ( return (
<div className={`group relative p-4 bg-white dark:bg-stone-900 rounded-xl border border-slate-100 dark:border-stone-700/50 border-l-[3px] ${borderColors[status]} shadow-sm hover:shadow-md transition-all duration-200 ${isStopped ? 'opacity-75 hover:opacity-100' : ''}`}> <div className={`group relative p-4 bg-white dark:bg-stone-900 rounded-xl border border-slate-100 dark:border-stone-700/50 border-l-[3px] ${borderColors[status]} shadow-sm hover:shadow-md transition-all duration-200 ${isStopped ? 'opacity-75 hover:opacity-100' : ''}`}>
{/* Top row: badges + links */} {/* Top row: badge */}
<div className="absolute top-3 right-3 flex items-center gap-1.5"> <div className="absolute top-3 right-3 flex items-center gap-1.5">
{resourceBadge && ( {resourceBadge && (
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-slate-100 dark:bg-stone-800 text-slate-500 dark:text-stone-400"> <span className="text-[10px] font-medium px-1.5 py-0.5 rounded bg-slate-100 dark:bg-stone-800 text-slate-500 dark:text-stone-400">
{resourceBadge} {resourceBadge}
</span> </span>
)} )}
{discovered && (
<a
href={getCoolifyUrl(service as DiscoveredService)}
target="_blank"
rel="noopener noreferrer"
title="Manage in Coolify"
className="p-1 rounded-md text-slate-300 dark:text-stone-600 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
>
<Icon name="settings" size={14} />
</a>
)}
</div> </div>
{/* Clickable area for opening service URL */} {/* Service info (non-clickable) */}
<a <div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-100 dark:bg-stone-800 mb-3">
href={service.url}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-100 dark:bg-stone-800 mb-3 group-hover:bg-slate-200 dark:group-hover:bg-stone-700 transition-colors">
<Icon <Icon
name={service.icon} name={service.icon}
size={20} size={20}
@@ -157,12 +140,11 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
</span> </span>
)} )}
</div> </div>
</a>
{/* Controls footer */} {/* Footer: status + links + controls */}
{discovered && (
<div className="mt-3 pt-3 border-t border-slate-100 dark:border-stone-800 flex items-center justify-between"> <div className="mt-3 pt-3 border-t border-slate-100 dark:border-stone-800 flex items-center justify-between">
{/* Status pill */} {/* Left: status pill */}
{discovered ? (
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium ${statusPillStyles[status]}`}> <span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium ${statusPillStyles[status]}`}>
<Icon <Icon
name={loading ? 'loader' : statusIcons[status]} name={loading ? 'loader' : statusIcons[status]}
@@ -171,10 +153,44 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
/> />
{loading ? 'Processing...' : statusLabels[status]} {loading ? 'Processing...' : statusLabels[status]}
</span> </span>
) : (
<span />
)}
{/* Action buttons */} {/* Right: links + action buttons */}
{!loading && (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{/* Open website */}
<a
href={service.url}
target="_blank"
rel="noopener noreferrer"
title="Open website"
className="p-1.5 rounded-md text-slate-400 dark:text-stone-500 hover:bg-slate-100 dark:hover:bg-stone-800 hover:text-slate-600 dark:hover:text-stone-300 transition-colors"
>
<Icon name="external-link" size={14} />
</a>
{/* Manage in Coolify */}
{discovered && (
<a
href={getCoolifyUrl(service as DiscoveredService)}
target="_blank"
rel="noopener noreferrer"
title="Manage in Coolify"
className="p-1.5 rounded-md text-slate-400 dark:text-stone-500 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
>
<Icon name="settings" size={14} />
</a>
)}
{/* Divider between links and controls */}
{discovered && !loading && (status === 'running' || isStopped) && (
<span className="w-px h-4 bg-slate-200 dark:bg-stone-700 mx-0.5" />
)}
{/* Control buttons */}
{discovered && !loading && (
<>
{isStopped ? ( {isStopped ? (
<button <button
onClick={() => controlService('start')} onClick={() => controlService('start')}
@@ -205,10 +221,10 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
</button> </button>
</> </>
) : null} ) : null}
</div> </>
)} )}
</div> </div>
)} </div>
</div> </div>
); );
} }