feat: complete redesign of admin plan cards

Major UI Overhaul:
 Modern card design with gradients
 Hover effects: lift up + shadow
 Inline layout: grip icon + title + badge
 No wasted vertical space
 Larger price display (text-4xl)
 Gradient price section with border
 2-column stats grid with cards
 Animated pulse dot for active status
 Full-width visibility button + icon actions
 Indonesian text throughout

Layout Changes:
- Grip icon inline with title (left side)
- Badge inline with title text
- Trial badge inline with duration
- Stats in grid layout
- Action buttons with hover fills

Design Elements:
- rounded-2xl cards
- Gradient backgrounds
- Border color changes on hover
- Smooth transitions (300ms)
- Better spacing and typography
- Consistent card heights
This commit is contained in:
dwindown
2025-10-11 21:59:13 +07:00
parent bef2344ddc
commit 1e3d320716

View File

@@ -125,95 +125,117 @@ export function AdminPlans() {
{plans.map((plan) => ( {plans.map((plan) => (
<div <div
key={plan.id} key={plan.id}
className="bg-card rounded-lg shadow border border-border overflow-hidden" className="group relative bg-gradient-to-br from-card to-card/50 rounded-2xl border-2 border-border/50 hover:border-primary/30 overflow-hidden transition-all duration-300 hover:shadow-2xl hover:-translate-y-1"
> >
{/* Header */} {/* Content */}
<div className="p-6 border-b border-border"> <div className="p-6">
<div className="flex items-start justify-between mb-2"> {/* Header: Drag Icon + Title/Description + Badge */}
<div className="flex items-center"> <div className="flex items-start gap-3 mb-6">
<GripVertical className="h-5 w-5 text-muted-foreground mr-2 cursor-move" /> {/* Drag Handle */}
<h3 className="text-lg font-semibold text-foreground"> <div className="opacity-0 group-hover:opacity-100 transition-opacity pt-1">
<GripVertical className="h-5 w-5 mt-1 text-muted-foreground/50 cursor-move" />
</div>
{/* Title & Description */}
<div className="flex-1 min-w-0">
{/* Plan Name + Badge */}
<div className="flex items-center gap-2 mb-2 flex-wrap">
<h3 className="text-2xl font-bold text-foreground">
{plan.name} {plan.name}
</h3> </h3>
</div>
{plan.badge && ( {plan.badge && (
<span <span
className={`text-xs font-semibold inline-flex items-center rounded-md px-2.5 py-0.5 ${ className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold shadow-sm ${
plan.badgeColor === 'blue' plan.badgeColor === 'blue'
? 'bg-primary/20 text-primary ring-1 ring-primary' ? 'bg-gradient-to-r from-blue-500 to-blue-600 text-white'
: plan.badgeColor === 'green' : plan.badgeColor === 'green'
? 'bg-green-500/20 text-green-600 ring-1 ring-green-600' ? 'bg-gradient-to-r from-green-500 to-green-600 text-white'
: 'bg-muted text-muted-foreground ring-1 ring-border' : 'bg-muted text-muted-foreground'
}`} }`}
> >
{plan.badge} {plan.badge}
</span> </span>
)} )}
</div> </div>
<p className="text-sm text-muted-foreground">
{/* Description */}
<p className="text-sm text-muted-foreground line-clamp-2">
{plan.description} {plan.description}
</p> </p>
</div> </div>
</div>
{/* Price */} {/* Price - Hero Section */}
<div className="p-6 bg-muted"> <div className="mb-6 p-6 rounded-xl bg-gradient-to-br from-primary/5 to-primary/10 border border-primary/20">
<div className="text-3xl font-bold text-foreground"> <div className="flex items-baseline gap-2 mb-1">
<span className="text-4xl font-black text-foreground tracking-tight">
{formatPrice(plan.price, plan.currency)} {formatPrice(plan.price, plan.currency)}
</span>
</div> </div>
<div className="text-sm text-muted-foreground mt-1"> <div className="flex items-center gap-2 flex-wrap">
<p className="text-sm font-medium text-muted-foreground">
{plan.durationType === 'lifetime' {plan.durationType === 'lifetime'
? 'Lifetime access' ? 'Akses Selamanya'
: plan.durationType === 'monthly' : plan.durationType === 'monthly'
? 'per month' ? 'per bulan'
: plan.durationType === 'yearly' : plan.durationType === 'yearly'
? 'per year' ? 'per tahun'
: plan.durationType} : plan.durationType}
</div> </p>
{plan.trialDays > 0 && ( {plan.trialDays > 0 && (
<div className="text-sm text-primary mt-1"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-primary/20 text-primary text-xs font-semibold">
{plan.trialDays} days free trial 🎁 {plan.trialDays} hari gratis
</div> </span>
)} )}
</div> </div>
</div>
{/* Stats */} {/* Stats Grid */}
<div className="p-6 border-t border-border"> <div className="grid grid-cols-2 gap-4 mb-6">
<div className="flex items-center justify-between text-sm"> <div className="p-3 rounded-lg bg-muted/50 border border-border/50">
<span className="text-muted-foreground"> <div className="text-xs text-muted-foreground mb-1">Subscribers</div>
Subscriptions: <div className="text-2xl font-bold text-foreground">
</span>
<span className="font-medium text-foreground">
{plan._count.subscriptions} {plan._count.subscriptions}
</span>
</div> </div>
<div className="flex items-center justify-between text-sm mt-2"> </div>
<span className="text-muted-foreground">Status:</span> <div className="p-3 rounded-lg bg-muted/50 border border-border/50">
<div className="flex items-center space-x-2"> <div className="text-xs text-muted-foreground mb-1">Status</div>
{plan.isActive && ( <div className="flex items-center gap-2">
<span className="px-2 py-1 text-xs bg-green-500/20 text-green-600 rounded"> {plan.isActive ? (
Active <>
</span> <div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
)} <span className="text-sm font-semibold text-green-600">Active</span>
{plan.isVisible ? ( </>
<Eye className="h-4 w-4 text-green-600" />
) : ( ) : (
<EyeOff className="h-4 w-4 text-muted-foreground" /> <>
<div className="h-2 w-2 rounded-full bg-gray-400" />
<span className="text-sm font-semibold text-muted-foreground">Inactive</span>
</>
)} )}
</div> </div>
</div> </div>
</div> </div>
{/* Actions */} {/* Action Buttons */}
<div className="p-4 bg-muted border-t border-border flex items-center justify-end space-x-2"> <div className="flex items-center gap-2">
<button <button
onClick={() => toggleVisibility(plan)} onClick={() => toggleVisibility(plan)}
className="p-2 text-muted-foreground hover:text-foreground transition-colors" className={`flex-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg font-medium text-sm transition-all ${
title={plan.isVisible ? 'Hide' : 'Show'} plan.isVisible
? 'bg-primary/10 text-primary hover:bg-primary/20'
: 'bg-muted text-muted-foreground hover:bg-muted/80'
}`}
> >
{plan.isVisible ? ( {plan.isVisible ? (
<EyeOff className="h-4 w-4" /> <>
) : (
<Eye className="h-4 w-4" /> <Eye className="h-4 w-4" />
<span>Visible</span>
</>
) : (
<>
<EyeOff className="h-4 w-4" />
<span>Hidden</span>
</>
)} )}
</button> </button>
<button <button
@@ -221,20 +243,21 @@ export function AdminPlans() {
setEditingPlan(plan) setEditingPlan(plan)
setShowModal(true) setShowModal(true)
}} }}
className="p-2 text-primary hover:text-primary/80 transition-colors" className="p-2.5 rounded-lg bg-primary/10 text-primary hover:bg-primary hover:text-primary-foreground transition-all"
title="Edit" title="Edit Plan"
> >
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
</button> </button>
<button <button
onClick={() => handleDelete(plan.id)} onClick={() => handleDelete(plan.id)}
className="p-2 text-destructive hover:text-destructive/80 transition-colors" className="p-2.5 rounded-lg bg-destructive/10 text-destructive hover:bg-destructive hover:text-destructive-foreground transition-all"
title="Delete" title="Hapus Plan"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
</button> </button>
</div> </div>
</div> </div>
</div>
))} ))}
</div> </div>