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