theme-provider #18

Merged
andika merged 6 commits from theme-provider into dev 2026-01-21 04:58:57 +00:00
4 changed files with 278 additions and 218 deletions
Showing only changes of commit c73ebfb6c0 - Show all commits

View File

@@ -0,0 +1,36 @@
import React from "react";
import { Card } from "./ui/card";
export const StatCard = ({
title,
value,
icon: Icon,
iconBgClass,
dataTestId,
}) => (
<Card
className="p-6 flex flex-col justify-between bg-background rounded-2xl border border-[var(--neutral-800)]"
data-testid={dataTestId}
>
<div className="flex items-start gap-4 mb-4 ">
<div className={`${iconBgClass} p-3 rounded-lg `}>
<Icon className="size-8" />
</div>
<div className="space-y-8">
<p
className="text-6xl font-semibold text-[var(--purple-ink)] mb-1"
style={{ fontFamily: "'Inter', sans-serif" }}
>
{value}
</p>
</div>
</div>
<p
className="text-sm text-brand-purple "
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
>
{title}
</p>
</Card>
);

View File

@@ -1,50 +1,65 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Card = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
{...props} />
))
Card.displayName = "Card"
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className,
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} />
))
CardHeader.displayName = "CardHeader"
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props} />
))
CardTitle.displayName = "CardTitle"
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props} />
))
CardDescription.displayName = "CardDescription"
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props} />
))
CardFooter.displayName = "CardFooter"
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@@ -4,13 +4,16 @@ import api from '../../utils/api';
import { Card } from '../../components/ui/card';
import { Button } from '../../components/ui/button';
import { Badge } from '../../components/ui/badge';
import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle, Globe } from 'lucide-react';
import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle, Globe, CircleMinus } from 'lucide-react';
import { StatCard } from '../../components/StatCard';
const AdminDashboard = () => {
const [stats, setStats] = useState({
totalMembers: 0,
pendingValidations: 0,
activeMembers: 0
activeMembers: 0,
inactiveMembers: 0
});
const [usersNeedingAttention, setUsersNeedingAttention] = useState([]);
const [loading, setLoading] = useState(true);
@@ -29,7 +32,8 @@ const AdminDashboard = () => {
pendingValidations: users.filter(u =>
['pending_email', 'pending_validation', 'pre_validated', 'payment_pending'].includes(u.status)
).length,
activeMembers: users.filter(u => u.status === 'active' && u.role === 'member').length
activeMembers: users.filter(u => u.status === 'active' && u.role === 'member').length,
inactiveMembers: users.filter(u => u.status === 'inactive' && u.role === 'member').length
});
// Find users who have received 3+ reminders (may need personal outreach)
@@ -76,52 +80,42 @@ const AdminDashboard = () => {
</div>
{/* Stats Grid */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]" data-testid="stat-total-users">
<div className='flex items-start justify-between'>
<p className="text-3xl font-semibold text-[var(--purple-ink)] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
{loading ? '-' : stats.totalMembers}
</p>
<div className="flex items-start justify-between mb-4">
<div className="bg-[var(--neutral-800)]/20 p-3 rounded-lg">
<Users className="h-6 w-6 text-brand-purple" />
</div>
<div className='rounded-3xl bg-brand-lavender/10 p-8 mb-8'>
<div className=' text-2xl text-[var(--purple-ink)] pb-8 font-semibold'>
Quick Overview
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8 ">
<StatCard
title="Total Members"
value={loading ? '-' : stats.totalMembers}
icon={Users}
iconBgClass="text-brand-purple"
dataTestId="stat-total-users"
/>
<StatCard
title="Pending Validations"
value={loading ? '-' : stats.pendingValidations}
icon={Clock}
iconBgClass="text-brand-light-orange"
dataTestId="stat-total-users"
/>
<StatCard
title="Active Members"
value={loading ? '-' : stats.activeMembers}
icon={CheckCircle}
iconBgClass="text-[var(--green-light)]"
dataTestId="stat-total-users"
/>
<StatCard
title="Inactive Members"
value={loading ? '-' : stats.inactiveMembers}
icon={CircleMinus}
iconBgClass="text-brand-pink"
dataTestId="stat-total-users"
/>
</div>
<p className="text-sm text-brand-purple" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
</Card>
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]" data-testid="stat-pending-validations">
<div className='flex items-start justify-between'>
<p className="text-3xl font-semibold text-[var(--purple-ink)] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
{loading ? '-' : stats.pendingValidations}
</p>
<div className="flex items-start justify-between mb-4">
<div className=" p-3 rounded-lg">
<Clock className="h-6 w-6 text-orange-600" />
</div>
</div>
</div>
<p className="text-sm text-brand-purple" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Validations</p>
</Card>
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]" data-testid="stat-active-members">
<div className='flex items-start justify-between'>
<p className="text-3xl font-semibold text-[var(--purple-ink)] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
{loading ? '-' : stats.activeMembers}
</p>
<div className="flex items-start justify-between mb-4">
<div className="bg-[var(--green-light)]/20 p-3 rounded-lg">
<CheckCircle className="h-6 w-6 text-[var(--green-light)]" />
</div>
</div>
</div>
<p className="text-sm text-brand-purple" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Active Members</p>
</Card>
</div>
{/* Quick Actions */}

View File

@@ -14,12 +14,13 @@ import {
DropdownMenuTrigger,
} from '../../components/ui/dropdown-menu';
import { toast } from 'sonner';
import { Users, Search, User, CreditCard, Eye, CheckCircle, Calendar, AlertCircle, Clock, Mail, UserPlus, Upload, Download, FileDown, ChevronDown } from 'lucide-react';
import { Users, Search, User, CreditCard, Eye, CheckCircle, Calendar, AlertCircle, Clock, Mail, UserPlus, Upload, Download, FileDown, ChevronDown, CircleMinus } from 'lucide-react';
import PaymentActivationDialog from '../../components/PaymentActivationDialog';
import ConfirmationDialog from '../../components/ConfirmationDialog';
import CreateMemberDialog from '../../components/CreateMemberDialog';
import InviteStaffDialog from '../../components/InviteStaffDialog';
import WordPressImportWizard from '../../components/WordPressImportWizard';
import { StatCard } from '@/components/StatCard';
const AdminMembers = () => {
const navigate = useNavigate();
@@ -319,31 +320,45 @@ const AdminMembers = () => {
</div>
{/* Stats */}
<div className='rounded-3xl bg-brand-lavender/10 p-8 mb-8'>
<div className=' text-2xl text-[var(--purple-ink)] pb-8 font-semibold'>
Quick Overview
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]">
<p className="text-sm text-brand-purple dark:text-brand-lavender mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
<p className="text-4xl font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
{users.length}
</p>
</Card>
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]">
<p className="text-sm text-brand-purple dark:text-brand-lavender mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Active</p>
<p className="text-4xl font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
{users.filter(u => u.status === 'active').length}
</p>
</Card>
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]">
<p className="text-sm text-brand-purple dark:text-brand-lavender mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Payment Pending</p>
<p className="text-4xl font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
{users.filter(u => u.status === 'payment_pending').length}
</p>
</Card>
<Card className="p-6 bg-background rounded-2xl border border-[var(--neutral-800)]">
<p className="text-sm text-brand-purple dark:text-brand-lavender mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Inactive</p>
<p className="text-4xl font-semibold text-[var(--purple-ink)]" style={{ fontFamily: "'Inter', sans-serif" }}>
{users.filter(u => u.status === 'inactive').length}
</p>
</Card>
<StatCard
title="Total Members"
value={users.length}
icon={Users}
iconBgClass="bg-[var(--blue-light)] text-[var(--blue-dark)]"
dataTestId="stat-total-members"
/>
<StatCard
title="Active"
value={users.filter(u => u.status === 'active').length}
icon={CheckCircle}
iconBgClass="text-[var(--green-light)]"
dataTestId="stat-active-members"
/>
<StatCard
title="Payment Pending"
value={users.filter(u => u.status === 'payment_pending').length}
icon={CreditCard}
iconBgClass="text-brand-light-orange"
dataTestId="stat-payment-pending-members"
/>
<StatCard
title="Inactive"
value={users.filter(u => u.status === 'inactive').length}
icon={CircleMinus}
iconBgClass=" text-brand-pink"
dataTestId="stat-inactive-members"
/>
</div>
</div>
{/* Filters */}