refactor: update styles in MembersDirectory and NewsletterArchive for consistency and improved theming

- Updated color classes to use CSS variables for better maintainability and theming.
- Refactored component styles in MembersDirectory.js to enhance visual consistency.
- Adjusted loading states and empty states in NewsletterArchive.js for improved user experience.
- Added new brand colors to tailwind.config.js for future use.
This commit is contained in:
2026-01-12 20:10:33 -06:00
parent a93e2aa863
commit 7694532d53
77 changed files with 2519 additions and 2338 deletions

View File

@@ -203,9 +203,9 @@ const AdminMembers = () => {
const config = {
pending_email: { label: 'Pending Email', className: 'bg-orange-100 text-orange-700' },
pending_validation: { label: 'Pending Validation', className: 'bg-gray-200 text-gray-700' },
pre_validated: { label: 'Pre-Validated', className: 'bg-[#81B29A] text-white' },
pre_validated: { label: 'Pre-Validated', className: 'bg-var(--green-light) text-white' },
payment_pending: { label: 'Payment Pending', className: 'bg-orange-500 text-white' },
active: { label: 'Active', className: 'bg-[#81B29A] text-white' },
active: { label: 'Active', className: 'bg-var(--green-light) text-white' },
inactive: { label: 'Inactive', className: 'bg-gray-400 text-white' },
canceled: { label: 'Canceled', className: 'bg-red-100 text-red-700' },
expired: { label: 'Expired', className: 'bg-red-500 text-white' },
@@ -234,9 +234,9 @@ const AdminMembers = () => {
renewalReminders,
totalReminders,
lastReminderAt: user.last_email_verification_reminder_at ||
user.last_event_attendance_reminder_at ||
user.last_payment_reminder_at ||
user.last_renewal_reminder_at
user.last_event_attendance_reminder_at ||
user.last_payment_reminder_at ||
user.last_renewal_reminder_at
};
};
@@ -245,10 +245,10 @@ const AdminMembers = () => {
<div className="mb-8">
<div className="flex justify-between items-start mb-4">
<div>
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
<h1 className="text-4xl md:text-5xl font-semibold text-var(--purple-ink) mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
Members Management
</h1>
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<p className="text-lg text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Manage paying members and their subscriptions.
</p>
</div>
@@ -257,7 +257,7 @@ const AdminMembers = () => {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl h-12 px-6"
className="bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white rounded-xl h-12 px-6"
disabled={exporting}
>
{exporting ? (
@@ -288,7 +288,7 @@ const AdminMembers = () => {
{hasPermission('users.import') && (
<Button
onClick={() => setImportDialogOpen(true)}
className="bg-[#81B29A] hover:bg-[#6DA085] text-white rounded-xl h-12 px-6"
className="bg-var(--green-light) hover:bg-var(--green-fern) text-white rounded-xl h-12 px-6"
>
<Upload className="h-5 w-5 mr-2" />
Import
@@ -298,7 +298,7 @@ const AdminMembers = () => {
{hasPermission('users.invite') && (
<Button
onClick={() => setInviteDialogOpen(true)}
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl h-12 px-6"
className="bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white rounded-xl h-12 px-6"
>
<Mail className="h-5 w-5 mr-2" />
Invite Member
@@ -308,7 +308,7 @@ const AdminMembers = () => {
{hasPermission('users.create') && (
<Button
onClick={() => setCreateDialogOpen(true)}
className="bg-[#81B29A] hover:bg-[#6DA085] text-white rounded-xl h-12 px-6"
className="bg-var(--green-light) hover:bg-var(--green-fern) text-white rounded-xl h-12 px-6"
>
<UserPlus className="h-5 w-5 mr-2" />
Create Member
@@ -320,47 +320,47 @@ const AdminMembers = () => {
{/* Stats */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]">
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)">
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
{users.length}
</p>
</Card>
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]">
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Active</p>
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)">
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Active</p>
<p className="text-3xl 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-white rounded-2xl border border-[#ddd8eb]">
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Payment Pending</p>
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)">
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Payment Pending</p>
<p className="text-3xl 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-white rounded-2xl border border-[#ddd8eb]">
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Inactive</p>
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)">
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Inactive</p>
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
{users.filter(u => u.status === 'inactive').length}
</p>
</Card>
</div>
{/* Filters */}
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] mb-8">
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800) mb-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="relative">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-var(--purple-lavender)" />
<Input
placeholder="Search by name or email..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
className="pl-12 h-14 rounded-xl border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
data-testid="search-members-input"
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]" data-testid="status-filter-select">
<SelectTrigger className="h-14 rounded-xl border-2 border-var(--neutral-800)" data-testid="status-filter-select">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
@@ -381,32 +381,32 @@ const AdminMembers = () => {
{/* Members List */}
{loading ? (
<div className="text-center py-20">
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading members...</p>
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading members...</p>
</div>
) : filteredUsers.length > 0 ? (
<div className="space-y-4">
{filteredUsers.map((user) => (
<Card
key={user.id}
className="p-6 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-md transition-shadow"
className="p-6 bg-background rounded-2xl border border-var(--neutral-800) hover:shadow-md transition-shadow"
data-testid={`member-card-${user.id}`}
>
<div className="flex justify-between items-start flex-wrap gap-4">
<div className="flex items-start gap-4 flex-1">
{/* Avatar */}
<div className="h-14 w-14 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] font-semibold text-lg flex-shrink-0">
<div className="h-14 w-14 rounded-full bg-var(--neutral-800) flex items-center justify-center text-var(--purple-ink) font-semibold text-lg flex-shrink-0">
{user.first_name?.[0]}{user.last_name?.[0]}
</div>
{/* Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2 flex-wrap">
<h3 className="text-xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
<h3 className="text-xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
{user.first_name} {user.last_name}
</h3>
{getStatusBadge(user.status)}
</div>
<div className="grid md:grid-cols-2 gap-2 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<div className="grid md:grid-cols-2 gap-2 text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
<p>Joined: {new Date(user.created_at).toLocaleDateString()}</p>
@@ -420,19 +420,19 @@ const AdminMembers = () => {
const reminderInfo = getReminderInfo(user);
if (reminderInfo.totalReminders > 0) {
return (
<div className="mt-4 p-3 bg-[#F8F7FB] rounded-lg border border-[#ddd8eb]">
<div className="mt-4 p-3 bg-var(--lavender-500) rounded-lg border border-var(--neutral-800)">
<div className="flex items-center gap-2 mb-2">
<AlertCircle className="h-4 w-4 text-[#ff9e77]" />
<span className="text-sm font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
<AlertCircle className="h-4 w-4 text-var(--orange-light)" />
<span className="text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
{reminderInfo.totalReminders} reminder{reminderInfo.totalReminders !== 1 ? 's' : ''} sent
{reminderInfo.totalReminders >= 3 && (
<Badge className="ml-2 bg-[#ff9e77] text-white px-2 py-0.5 rounded-full text-xs">
<Badge className="ml-2 bg-var(--orange-light) text-white px-2 py-0.5 rounded-full text-xs">
Needs attention
</Badge>
)}
</span>
</div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-xs text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-xs text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{reminderInfo.emailReminders > 0 && (
<p>
<Mail className="inline h-3 w-3 mr-1" />
@@ -459,7 +459,7 @@ const AdminMembers = () => {
)}
</div>
{reminderInfo.lastReminderAt && (
<p className="mt-2 text-xs text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<p className="mt-2 text-xs text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Last reminder: {new Date(reminderInfo.lastReminderAt).toLocaleDateString()} at {new Date(reminderInfo.lastReminderAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</p>
)}
@@ -478,7 +478,7 @@ const AdminMembers = () => {
<Button
variant="outline"
size="sm"
className="border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white"
className="border-var(--purple-lavender) text-var(--purple-lavender) hover:bg-var(--purple-lavender) hover:text-white"
>
<Eye className="h-4 w-4 mr-1" />
View Profile
@@ -490,7 +490,7 @@ const AdminMembers = () => {
<Button
onClick={() => handleActivatePayment(user)}
size="sm"
className="bg-[#DDD8EB] text-[#422268] hover:bg-white"
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background"
>
<CheckCircle className="h-4 w-4 mr-1" />
Activate Payment
@@ -500,7 +500,7 @@ const AdminMembers = () => {
{/* Status Management */}
<div className="flex items-center gap-2">
<span className="text-sm text-[#664fa3] whitespace-nowrap" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<span className="text-sm text-var(--purple-lavender) whitespace-nowrap" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Change Status:
</span>
<Select
@@ -508,7 +508,7 @@ const AdminMembers = () => {
onValueChange={(newStatus) => handleStatusChangeRequest(user.id, user.status, newStatus, user)}
disabled={statusChanging === user.id}
>
<SelectTrigger className="w-[180px] h-9 border-[#ddd8eb]">
<SelectTrigger className="w-[180px] h-9 border-var(--neutral-800)">
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -527,11 +527,11 @@ const AdminMembers = () => {
</div>
) : (
<div className="text-center py-20">
<Users className="h-20 w-20 text-[#ddd8eb] mx-auto mb-6" />
<h3 className="text-2xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
<Users className="h-20 w-20 text-var(--neutral-800) mx-auto mb-6" />
<h3 className="text-2xl font-semibold text-var(--purple-ink) mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
No Members Found
</h3>
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{searchQuery || statusFilter !== 'all'
? 'Try adjusting your filters'
: 'No members yet'}