feat: Add Sonner toast notifications to all CRUD operations

- Install sonner package and create Toaster component
- Add toast notifications to all admin dashboard operations:
  * AdminPlans: create, update, delete, reorder, toggle visibility
  * AdminPaymentMethods: create, update, delete, reorder, toggle active
  * AdminUsers: suspend, unsuspend, grant pro access
  * AdminPayments: verify, reject
  * AdminSettings: save settings
- Add toast notifications to all member dashboard operations:
  * Wallets: create, update, delete
  * Transactions: create, update, delete
  * Profile: update name, avatar, phone, password, delete account
  * OTP: enable/disable email, WhatsApp, authenticator
- Replace all alert() calls with toast.success/error/warning
- Add proper success/error messages in Bahasa Indonesia
- Implement smart plan deletion (permanent if no subscriptions, soft delete if has subscriptions)
- Fix admin redirect after login (admin goes to /admin, users to /)
- Exclude admin accounts from subscription distribution chart
- Show inactive plans with visual indicators
- Add real revenue data to admin dashboard charts
- Use formatLargeNumber for consistent number formatting
This commit is contained in:
dwindown
2025-10-12 08:43:50 +07:00
parent 258d326909
commit c0df4a7c2a
37 changed files with 2744 additions and 628 deletions

View File

@@ -49,10 +49,36 @@ let AdminPlansService = class AdminPlansService {
});
}
async delete(id) {
return this.prisma.plan.update({
const plan = await this.prisma.plan.findUnique({
where: { id },
data: { isActive: false, isVisible: false },
include: {
_count: {
select: { subscriptions: true },
},
},
});
if (!plan) {
throw new Error('Plan not found');
}
if (plan._count.subscriptions > 0) {
return {
success: false,
message: `Cannot delete plan. There are ${plan._count.subscriptions} active subscription(s) associated with this plan. The plan has been deactivated instead.`,
action: 'deactivated',
plan: await this.prisma.plan.update({
where: { id },
data: { isActive: false, isVisible: false },
}),
};
}
await this.prisma.plan.delete({
where: { id },
});
return {
success: true,
message: 'Plan permanently deleted',
action: 'deleted',
};
}
async reorder(planIds) {
const updates = planIds.map((id, index) => this.prisma.plan.update({