Donation status badge upates, admin validation tootips

This commit is contained in:
2026-01-29 19:37:41 -06:00
parent de719d9d69
commit d5152609b6
4 changed files with 156 additions and 132 deletions

View File

@@ -6,7 +6,6 @@ const STATUS_BADGE_CONFIG = {
//status-based badges
pending_email: { label: 'Pending Email', variant: 'orange2' },
pending_validation: { label: 'Pending Validation', variant: 'gray' },
pre_validated: { label: 'Pre-Validated', variant: 'green' },
payment_pending: { label: 'Payment Pending', variant: 'orange' },
active: { label: 'Active', variant: 'green' },
inactive: { label: 'Inactive', variant: 'gray2' },
@@ -23,7 +22,12 @@ const STATUS_BADGE_CONFIG = {
admin: { label: 'Admin', variant: 'purple' },
moderator: { label: 'Moderator', variant: 'bg-[var(--neutral-800)] text-[var(--purple-ink)]' },
staff: { label: 'Staff', variant: 'gray' },
media: { label: 'Media', variant: 'gray2' }
media: { label: 'Media', variant: 'gray2' },
//donation badges
pending: { label: 'Payment Pending', variant: 'orange' },
completed: { label: 'Completed', variant: 'green' },
failed: { label: 'Failed', className: 'bg-red-100 text-red-700' }
};
//todo: make shield icon dynamic based on status

View File

@@ -1,78 +1,91 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Table = React.forwardRef(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props} />
<table ref={ref} className={cn("w-full", className)} {...props} />
</div>
))
Table.displayName = "Table"
));
Table.displayName = "Table";
const TableHeader = React.forwardRef(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
<thead
ref={ref}
className={cn(
"bg-[var(--lavender-300)] border-b border-[var(--neutral-800)]",
className,
)}
{...props}
/>
));
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props} />
))
TableBody.displayName = "TableBody"
{...props}
/>
));
TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
{...props} />
))
TableFooter.displayName = "TableFooter"
className={cn(
"border-t border-[var(--neutral-800)] font-medium [&>tr]:last:border-b-0",
className,
)}
{...props}
/>
));
TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
"border-b border-[var(--neutral-800)] transition-colors hover:bg-[var(--lavender-400)]",
className,
)}
{...props} />
))
TableRow.displayName = "TableRow"
{...props}
/>
));
TableRow.displayName = "TableRow";
const TableHead = React.forwardRef(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
"p-4 text-left align-middle font-semibold text-[var(--purple-ink)] [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className,
)}
{...props} />
))
TableHead.displayName = "TableHead"
{...props}
/>
));
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
"p-4 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] ",
className,
)}
{...props} />
))
TableCell.displayName = "TableCell"
{...props}
/>
));
TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props} />
))
TableCaption.displayName = "TableCaption"
{...props}
/>
));
TableCaption.displayName = "TableCaption";
export {
Table,
@@ -83,4 +96,4 @@ export {
TableRow,
TableCell,
TableCaption,
}
};

View File

@@ -3,6 +3,7 @@ import { useAuth } from '../../context/AuthContext';
import { Card } from '../../components/ui/card';
import { Button } from '../../components/ui/button';
import { Input } from '../../components/ui/input';
import StatusBadge from '@/components/StatusBadge';
import {
Select,
SelectContent,
@@ -17,6 +18,14 @@ import {
DropdownMenuTrigger,
} from '../../components/ui/dropdown-menu';
import { Badge } from '../../components/ui/badge';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../../components/ui/table';
import api from '../../utils/api';
import { toast } from 'sonner';
import {
@@ -184,15 +193,8 @@ const AdminDonations = () => {
toast.error('Failed to copy to clipboard');
}
};
const getStatusBadgeVariant = (status) => {
const variants = {
completed: 'default',
pending: 'secondary',
failed: 'destructive'
};
return variants[status] || 'outline';
};
/*
*/
const getTypeBadgeColor = (type) => {
return type === 'member' ? 'bg-[var(--green-light)]' : 'bg-brand-purple ';
@@ -392,51 +394,37 @@ const AdminDonations = () => {
{/* Donations Table */}
<Card className="bg-background rounded-2xl border-2 border-[var(--neutral-800)] overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-[var(--lavender-300)] border-b-2 border-[var(--neutral-800)]">
<tr>
<th className="px-6 py-4 text-left text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Donor
</th>
<th className="px-6 py-4 text-left text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Type
</th>
<th className="px-6 py-4 text-left text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Amount
</th>
<th className="px-6 py-4 text-left text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Status
</th>
<th className="px-6 py-4 text-left text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Date
</th>
<th className="px-6 py-4 text-left text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Payment Method
</th>
<th className="px-6 py-4 text-center text-sm font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
Details
</th>
</tr>
</thead>
<tbody className="divide-y divide-[var(--neutral-800)]">
<Table>
<TableHeader>
<TableRow>
<TableHead>Donor</TableHead>
<TableHead>Type</TableHead>
<TableHead>Amount</TableHead>
<TableHead>Status</TableHead>
<TableHead>Date</TableHead>
<TableHead>Payment Method</TableHead>
<TableHead className="text-center">Details</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredDonations.length === 0 ? (
<tr>
<td colSpan="7" className="px-6 py-12 text-center">
<TableRow>
<TableCell colSpan={7} className="p-12 text-center">
<div className="flex flex-col items-center gap-3">
<Heart className="h-12 w-12 text-[var(--neutral-800)]" />
<p className="text-brand-purple " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{donations.length === 0 ? 'No donations yet' : 'No donations match your filters'}
</p>
</div>
</td>
</tr>
</TableCell>
</TableRow>
) : (
filteredDonations.map((donation) => {
const isExpanded = expandedRows.has(donation.id);
return (
<React.Fragment key={donation.id}>
<tr className="hover:bg-[var(--lavender-400)] transition-colors">
<td className="px-6 py-4">
<TableRow>
<TableCell>
<div>
<p className="font-medium text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
{donation.donor_name || 'Anonymous'}
@@ -445,39 +433,37 @@ const AdminDonations = () => {
{donation.donor_email || 'No email'}
</p>
</div>
</td>
<td className="px-6 py-4">
</TableCell>
<TableCell>
<Badge
className={`${getTypeBadgeColor(donation.donation_type)} text-white border-none rounded-full px-3 py-1`}
className={`${getTypeBadgeColor(donation.donation_type)} text-white border-none px-3 py-1`}
style={{ fontFamily: "'Inter', sans-serif" }}
>
{donation.donation_type === 'member' ? 'Member' : 'Public'}
</Badge>
</td>
<td className="px-6 py-4">
</TableCell>
<TableCell>
<p className="font-semibold text-[var(--purple-ink)] text-lg" style={{ fontFamily: "'Inter', sans-serif" }}>
{donation.amount}
</p>
</td>
<td className="px-6 py-4">
<Badge variant={getStatusBadgeVariant(donation.status)} className="rounded-full">
{donation.status.charAt(0).toUpperCase() + donation.status.slice(1)}
</Badge>
</td>
<td className="px-6 py-4">
</TableCell>
<TableCell>
<StatusBadge status={donation.status} />
</TableCell>
<TableCell>
<div className="flex items-center gap-2 text-brand-purple ">
<Calendar className="h-4 w-4" />
<span style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{formatDate(donation.created_at)}
</span>
</div>
</td>
<td className="px-6 py-4">
<p className="text-brand-purple " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
</TableCell>
<TableCell>
<p className="text-brand-purple " style={{ fontFamily: "'Nunito Sans', sans-serif capitalize" }}>
{donation.payment_method || 'N/A'}
</p>
</td>
<td className="px-6 py-4 text-center">
</TableCell>
<TableCell className="text-center">
<Button
size="sm"
variant="ghost"
@@ -486,12 +472,11 @@ const AdminDonations = () => {
>
{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>
</td>
</tr>
{/* Expandable Details Row */}
</TableCell>
</TableRow>
{isExpanded && (
<tr className="bg-[var(--lavender-400)]/30">
<td colSpan="7" className="px-6 py-6">
<TableRow className="bg-[var(--lavender-400)]/30">
<TableCell colSpan={7} className="p-6">
<div className="space-y-4">
<h4 className="font-semibold text-[var(--purple-ink)] text-lg mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
Transaction Details
@@ -601,15 +586,15 @@ const AdminDonations = () => {
</div>
</div>
</div>
</td>
</tr>
</TableCell>
</TableRow>
)}
</React.Fragment>
);
})
)}
</tbody>
</table>
</TableBody>
</Table>
</div>
</Card>

View File

@@ -18,6 +18,12 @@ import {
TableRow,
TableCell,
} from '../../components/ui/table';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '../../components/ui/tooltip';
import {
Pagination,
PaginationContent,
@@ -417,7 +423,7 @@ const AdminValidations = () => {
<Card className="bg-background rounded-2xl border border-[var(--neutral-800)] overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableRow className="text-md">
<TableHead
className="cursor-pointer hover:bg-[var(--neutral-800)]/20"
onClick={() => handleSort('first_name')}
@@ -482,8 +488,8 @@ const AdminValidations = () => {
onValueChange={(action) => handleActionSelect(user, action)}
disabled={actionLoading === user.id || resendLoading === user.id}
>
<SelectTrigger className="w-[180px] h-9 border-[var(--neutral-800)]">
<SelectValue placeholder={actionLoading === user.id || resendLoading === user.id ? 'Processing...' : 'Select Action'} />
<SelectTrigger className="w-[100px] h-9 border-[var(--neutral-800)]">
<SelectValue placeholder={actionLoading === user.id || resendLoading === user.id ? 'Processing...' : 'Action'} />
</SelectTrigger>
<SelectContent>
{user.status === 'rejected' ? (
@@ -521,7 +527,10 @@ const AdminValidations = () => {
)}
</SelectContent>
</Select>
<TooltipProvider>
{/* view registration */}
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => handleRegistrationDialog(user)}
disabled={actionLoading === user.id}
@@ -531,9 +540,16 @@ const AdminValidations = () => {
>
<FileText className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
View registration
</TooltipContent>
</Tooltip>
{/* reject */}
{hasPermission('users.approve') && (
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => handleRejectUser(user)}
disabled={actionLoading === user.id}
@@ -543,7 +559,13 @@ const AdminValidations = () => {
>
X
</Button>
</TooltipTrigger>
<TooltipContent>
Reject user
</TooltipContent>
</Tooltip>
)}
</TooltipProvider>
</div>
</TableCell>