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:
@@ -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>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user