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:
@@ -162,14 +162,14 @@ const AdminBylaws = () => {
|
||||
};
|
||||
|
||||
const currentBylaws = bylaws.find(b => b.is_current);
|
||||
const historicalBylaws = bylaws.filter(b => !b.is_current).sort((a, b) =>
|
||||
const historicalBylaws = bylaws.filter(b => !b.is_current).sort((a, b) =>
|
||||
new Date(b.effective_date) - new Date(a.effective_date)
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="text-[#664fa3]">Loading bylaws...</p>
|
||||
<p className="text-var(--purple-lavender)">Loading bylaws...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -179,17 +179,17 @@ const AdminBylaws = () => {
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Bylaws Management
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage LOAF governing bylaws and version history
|
||||
</p>
|
||||
</div>
|
||||
{hasPermission('bylaws.create') && (
|
||||
<Button
|
||||
onClick={handleCreate}
|
||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||
className="bg-var(--purple-lavender) text-white hover:bg-var(--purple-muted) rounded-full flex items-center gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Version
|
||||
@@ -199,22 +199,22 @@ const AdminBylaws = () => {
|
||||
|
||||
{/* Current Bylaws */}
|
||||
{currentBylaws ? (
|
||||
<Card className="p-6 border-2 border-[#664fa3]">
|
||||
<Card className="p-6 border-2 border-var(--purple-lavender)">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-gradient-to-br from-[#664fa3] to-[#422268] p-3 rounded-xl">
|
||||
<div className="bg-gradient-to-br from-var(--purple-lavender) to-var(--purple-ink) p-3 rounded-xl">
|
||||
<Scale className="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-[#422268]">
|
||||
<h3 className="text-xl font-semibold text-var(--purple-ink)">
|
||||
{currentBylaws.title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge className="bg-[#81B29A] text-white">
|
||||
<Badge className="bg-var(--green-light) text-white">
|
||||
<Check className="h-3 w-3 mr-1" />
|
||||
Current Version
|
||||
</Badge>
|
||||
<span className="text-[#664fa3] text-sm">
|
||||
<span className="text-var(--purple-lavender) text-sm">
|
||||
Version {currentBylaws.version}
|
||||
</span>
|
||||
</div>
|
||||
@@ -225,7 +225,7 @@ const AdminBylaws = () => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => window.open(currentBylaws.document_url, '_blank')}
|
||||
className="border-[#664fa3] text-[#664fa3]"
|
||||
className="border-var(--purple-lavender) text-var(--purple-lavender)"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-1" />
|
||||
View
|
||||
@@ -235,7 +235,7 @@ const AdminBylaws = () => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(currentBylaws)}
|
||||
className="border-[#664fa3] text-[#664fa3]"
|
||||
className="border-var(--purple-lavender) text-var(--purple-lavender)"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -252,7 +252,7 @@ const AdminBylaws = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-[#664fa3]">
|
||||
<div className="flex items-center gap-4 text-sm text-var(--purple-lavender)">
|
||||
<span>Effective Date: <strong>{formatDate(currentBylaws.effective_date)}</strong></span>
|
||||
<span>•</span>
|
||||
<span>Document Type: <strong>{currentBylaws.document_type === 'upload' ? 'PDF Upload' : 'Link'}</strong></span>
|
||||
@@ -260,10 +260,10 @@ const AdminBylaws = () => {
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="p-12 text-center">
|
||||
<Scale className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||
<p className="text-[#664fa3] text-lg mb-4">No current bylaws set</p>
|
||||
<Scale className="h-16 w-16 text-var(--neutral-800) mx-auto mb-4" />
|
||||
<p className="text-var(--purple-lavender) text-lg mb-4">No current bylaws set</p>
|
||||
{hasPermission('bylaws.create') && (
|
||||
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
||||
<Button onClick={handleCreate} className="bg-var(--purple-lavender) text-white">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create Bylaws
|
||||
</Button>
|
||||
@@ -274,7 +274,7 @@ const AdminBylaws = () => {
|
||||
{/* Historical Versions */}
|
||||
{historicalBylaws.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-[#422268] mb-4">
|
||||
<h2 className="text-xl font-semibold text-var(--purple-ink) mb-4">
|
||||
Version History ({historicalBylaws.length})
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
@@ -282,10 +282,10 @@ const AdminBylaws = () => {
|
||||
<Card key={bylawsDoc.id} className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-1">
|
||||
<h3 className="text-lg font-semibold text-var(--purple-ink) mb-1">
|
||||
{bylawsDoc.title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3 text-sm text-[#664fa3]">
|
||||
<div className="flex items-center gap-3 text-sm text-var(--purple-lavender)">
|
||||
<span>Version {bylawsDoc.version}</span>
|
||||
<span>•</span>
|
||||
<span>Effective {formatDate(bylawsDoc.effective_date)}</span>
|
||||
@@ -296,7 +296,7 @@ const AdminBylaws = () => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => window.open(bylawsDoc.document_url, '_blank')}
|
||||
className="border-[#664fa3] text-[#664fa3]"
|
||||
className="border-var(--purple-lavender) text-var(--purple-lavender)"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -305,7 +305,7 @@ const AdminBylaws = () => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(bylawsDoc)}
|
||||
className="border-[#664fa3] text-[#664fa3]"
|
||||
className="border-var(--purple-lavender) text-var(--purple-lavender)"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -404,12 +404,12 @@ const AdminBylaws = () => {
|
||||
required={!selectedBylaws}
|
||||
/>
|
||||
{uploadedFile && (
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Selected: {uploadedFile.name}
|
||||
</p>
|
||||
)}
|
||||
{selectedBylaws && !uploadedFile && (
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Current file will be kept if no new file is selected
|
||||
</p>
|
||||
)}
|
||||
@@ -424,7 +424,7 @@ const AdminBylaws = () => {
|
||||
placeholder="https://docs.google.com/... or https://example.com/file.pdf"
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Paste the shareable link to your document (Google Drive, Dropbox, PDF URL, etc.)
|
||||
</p>
|
||||
</div>
|
||||
@@ -455,7 +455,7 @@ const AdminBylaws = () => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-[#664fa3] text-white"
|
||||
className="bg-var(--purple-lavender) text-white"
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? 'Saving...' : selectedBylaws ? 'Update' : 'Create'}
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 } from 'lucide-react';
|
||||
|
||||
const AdminDashboard = () => {
|
||||
const [stats, setStats] = useState({
|
||||
@@ -58,16 +58,16 @@ const AdminDashboard = () => {
|
||||
<>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className="mb-8">
|
||||
<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" }}>
|
||||
Admin Dashboard
|
||||
</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 users, events, and membership applications.
|
||||
</p>
|
||||
</div>
|
||||
<Link to={'/'}>
|
||||
<Button
|
||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||
className="bg-var(--purple-lavender) text-white hover:bg-var(--purple-muted) rounded-full flex items-center gap-2"
|
||||
>
|
||||
<Globe />
|
||||
View Public Site
|
||||
@@ -77,56 +77,56 @@ const AdminDashboard = () => {
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-12">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]" data-testid="stat-total-users">
|
||||
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)" data-testid="stat-total-users">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="bg-[#DDD8EB]/20 p-3 rounded-lg">
|
||||
<Users className="h-6 w-6 text-[#664fa3]" />
|
||||
<div className="bg-var(--neutral-800)/20 p-3 rounded-lg">
|
||||
<Users className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-3xl font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink) mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{loading ? '-' : stats.totalMembers}
|
||||
</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]" data-testid="stat-pending-validations">
|
||||
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)" data-testid="stat-pending-validations">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="bg-orange-100 p-3 rounded-lg">
|
||||
<Clock className="h-6 w-6 text-orange-600" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-3xl font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink) mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{loading ? '-' : stats.pendingValidations}
|
||||
</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Validations</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Validations</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]" data-testid="stat-active-members">
|
||||
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800)" data-testid="stat-active-members">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="bg-[#81B29A]/20 p-3 rounded-lg">
|
||||
<CheckCircle className="h-6 w-6 text-[#81B29A]" />
|
||||
<div className="bg-var(--green-light)/20 p-3 rounded-lg">
|
||||
<CheckCircle className="h-6 w-6 text-var(--green-light)" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-3xl font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink) mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{loading ? '-' : stats.activeMembers}
|
||||
</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Active Members</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Active Members</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
<Link to="/admin/members">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-lg hover:-translate-y-1 transition-all cursor-pointer" data-testid="quick-action-users">
|
||||
<Users className="h-12 w-12 text-[#664fa3] mb-4" />
|
||||
<h3 className="text-xl font-semibold text-[#422268] mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Card className="p-8 bg-background rounded-2xl border border-var(--neutral-800) hover:shadow-lg hover:-translate-y-1 transition-all cursor-pointer" data-testid="quick-action-users">
|
||||
<Users className="h-12 w-12 text-var(--purple-lavender) mb-4" />
|
||||
<h3 className="text-xl font-semibold text-var(--purple-ink) mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Manage Members
|
||||
</h3>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
View and manage paying members and their subscription status.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
className="mt-4 bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full"
|
||||
data-testid="manage-users-button"
|
||||
>
|
||||
Go to Members
|
||||
@@ -135,16 +135,16 @@ const AdminDashboard = () => {
|
||||
</Link>
|
||||
|
||||
<Link to="/admin/validations">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-lg hover:-translate-y-1 transition-all cursor-pointer" data-testid="quick-action-validations">
|
||||
<Card className="p-8 bg-background rounded-2xl border border-var(--neutral-800) hover:shadow-lg hover:-translate-y-1 transition-all cursor-pointer" data-testid="quick-action-validations">
|
||||
<Clock className="h-12 w-12 text-orange-600 mb-4" />
|
||||
<h3 className="text-xl font-semibold text-[#422268] mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h3 className="text-xl font-semibold text-var(--purple-ink) mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Validation Queue
|
||||
</h3>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Review and validate pending membership applications.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
className="mt-4 bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full"
|
||||
data-testid="manage-validations-button"
|
||||
>
|
||||
View Validations
|
||||
@@ -156,16 +156,16 @@ const AdminDashboard = () => {
|
||||
{/* Users Needing Attention Widget */}
|
||||
{usersNeedingAttention.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<Card className="p-8 bg-white rounded-2xl border-2 border-[#ff9e77] shadow-lg">
|
||||
<Card className="p-8 bg-background rounded-2xl border-2 border-var(--orange-light) shadow-lg">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="bg-[#ff9e77]/20 p-3 rounded-lg">
|
||||
<AlertCircle className="h-6 w-6 text-[#ff9e77]" />
|
||||
<div className="bg-var(--orange-light)/20 p-3 rounded-lg">
|
||||
<AlertCircle className="h-6 w-6 text-var(--orange-light)" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h3 className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Members Needing Personal Outreach
|
||||
</h3>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
These members have received multiple reminder emails. Consider calling them directly.
|
||||
</p>
|
||||
</div>
|
||||
@@ -174,18 +174,18 @@ const AdminDashboard = () => {
|
||||
<div className="space-y-4">
|
||||
{usersNeedingAttention.map(user => (
|
||||
<Link key={user.id} to={`/admin/users/${user.id}`}>
|
||||
<div className="p-4 bg-[#F8F7FB] rounded-xl border border-[#ddd8eb] hover:border-[#ff9e77] hover:shadow-md transition-all cursor-pointer">
|
||||
<div className="p-4 bg-var(--lavender-500) rounded-xl border border-var(--neutral-800) hover:border-var(--orange-light) hover:shadow-md transition-all cursor-pointer">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h4 className="font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h4 className="font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{user.first_name} {user.last_name}
|
||||
</h4>
|
||||
<Badge className="bg-[#ff9e77] text-white px-3 py-1 rounded-full text-xs">
|
||||
<Badge className="bg-var(--orange-light) text-white px-3 py-1 rounded-full text-xs">
|
||||
{user.totalReminders} reminder{user.totalReminders !== 1 ? 's' : ''}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="grid grid-cols-1 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 || 'N/A'}</p>
|
||||
<p className="capitalize">Status: {user.status.replace('_', ' ')}</p>
|
||||
@@ -210,7 +210,7 @@ const AdminDashboard = () => {
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full text-sm"
|
||||
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full text-sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = `tel:${user.phone}`;
|
||||
@@ -224,8 +224,8 @@ const AdminDashboard = () => {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-4 bg-[#DDD8EB]/20 rounded-lg border border-[#ddd8eb]">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="mt-6 p-4 bg-var(--neutral-800)/20 rounded-lg border border-var(--neutral-800)">
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<strong>💡 Tip for helping older members:</strong> Many of our members are older ladies who may struggle with email.
|
||||
A friendly phone call can help them complete the registration process and feel more welcomed to the community.
|
||||
</p>
|
||||
|
||||
@@ -167,13 +167,13 @@ const AdminDonations = () => {
|
||||
};
|
||||
|
||||
const getTypeBadgeColor = (type) => {
|
||||
return type === 'member' ? 'bg-[#81B29A]' : 'bg-[#664fa3]';
|
||||
return type === 'member' ? 'bg-var(--green-light)' : 'bg-var(--purple-lavender)';
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-12 w-12 animate-spin text-[#664fa3]" />
|
||||
<Loader2 className="h-12 w-12 animate-spin text-var(--purple-lavender)" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -182,93 +182,93 @@ const AdminDonations = () => {
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Donation Management
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Track and manage all donations from members and the public
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid md:grid-cols-4 gap-6">
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Total Donations
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--purple-ink) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.total_donations || 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
|
||||
<Heart className="h-6 w-6 text-[#664fa3]" />
|
||||
<div className="p-3 bg-var(--neutral-800)/20 rounded-full">
|
||||
<Heart className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Member Donations
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#81B29A] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--green-light) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.member_donations || 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#81B29A]/10 rounded-full">
|
||||
<Users className="h-6 w-6 text-[#81B29A]" />
|
||||
<div className="p-3 bg-var(--green-light)/10 rounded-full">
|
||||
<Users className="h-6 w-6 text-var(--green-light)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Public Donations
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#664fa3] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.public_donations || 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
|
||||
<Globe className="h-6 w-6 text-[#664fa3]" />
|
||||
<div className="p-3 bg-var(--neutral-800)/20 rounded-full">
|
||||
<Globe className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Total Amount
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--purple-ink) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.total_amount || '$0.00'}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
|
||||
<DollarSign className="h-6 w-6 text-[#664fa3]" />
|
||||
<div className="p-3 bg-var(--neutral-800)/20 rounded-full">
|
||||
<DollarSign className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters and Actions */}
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="space-y-4">
|
||||
{/* Search and Export Row */}
|
||||
<div className="flex flex-col md:flex-row gap-4 justify-between">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-var(--purple-lavender)" />
|
||||
<Input
|
||||
placeholder="Search by donor name or email..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 rounded-full border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="pl-10 rounded-full border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
{hasPermission('donations.export') && (
|
||||
@@ -276,26 +276,26 @@ const AdminDonations = () => {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
disabled={exporting}
|
||||
className="bg-[#81B29A] text-white hover:bg-[#6a9680] rounded-full px-6 py-3 flex items-center gap-2"
|
||||
className="bg-var(--green-light) text-white hover:bg-var(--green-soft) rounded-full px-6 py-3 flex items-center gap-2"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{exporting ? 'Exporting...' : 'Export'}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56 bg-white rounded-xl border-2 border-[#ddd8eb] shadow-lg">
|
||||
<DropdownMenuContent align="end" className="w-56 bg-background rounded-xl border-2 border-var(--neutral-800) shadow-lg">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleExport('all')}
|
||||
className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3"
|
||||
className="cursor-pointer hover:bg-var(--lavender-300) rounded-lg p-3"
|
||||
>
|
||||
<FileDown className="h-4 w-4 mr-2 text-[#664fa3]" />
|
||||
<span className="text-[#422268]">Export All Donations</span>
|
||||
<FileDown className="h-4 w-4 mr-2 text-var(--purple-lavender)" />
|
||||
<span className="text-var(--purple-ink)">Export All Donations</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleExport('current')}
|
||||
className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3"
|
||||
className="cursor-pointer hover:bg-var(--lavender-300) rounded-lg p-3"
|
||||
>
|
||||
<FileDown className="h-4 w-4 mr-2 text-[#664fa3]" />
|
||||
<span className="text-[#422268]">Export Current View</span>
|
||||
<FileDown className="h-4 w-4 mr-2 text-var(--purple-lavender)" />
|
||||
<span className="text-var(--purple-ink)">Export Current View</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -306,7 +306,7 @@ const AdminDonations = () => {
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div>
|
||||
<Select value={typeFilter} onValueChange={setTypeFilter}>
|
||||
<SelectTrigger className="rounded-full border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="rounded-full border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="All Types" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -319,7 +319,7 @@ const AdminDonations = () => {
|
||||
|
||||
<div>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="rounded-full border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="rounded-full border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="All Statuses" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -336,7 +336,7 @@ const AdminDonations = () => {
|
||||
type="date"
|
||||
value={startDate}
|
||||
onChange={(e) => setStartDate(e.target.value)}
|
||||
className="rounded-full border-2 border-[#ddd8eb]"
|
||||
className="rounded-full border-2 border-var(--neutral-800)"
|
||||
placeholder="Start Date"
|
||||
/>
|
||||
</div>
|
||||
@@ -346,7 +346,7 @@ const AdminDonations = () => {
|
||||
type="date"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
className="rounded-full border-2 border-[#ddd8eb]"
|
||||
className="rounded-full border-2 border-var(--neutral-800)"
|
||||
placeholder="End Date"
|
||||
/>
|
||||
</div>
|
||||
@@ -354,7 +354,7 @@ const AdminDonations = () => {
|
||||
|
||||
{/* Active Filters Summary */}
|
||||
{(searchQuery || typeFilter !== 'all' || statusFilter !== 'all' || startDate || endDate) && (
|
||||
<div className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Showing {filteredDonations.length} of {donations.length} donations
|
||||
</div>
|
||||
)}
|
||||
@@ -362,38 +362,38 @@ const AdminDonations = () => {
|
||||
</Card>
|
||||
|
||||
{/* Donations Table */}
|
||||
<Card className="bg-white rounded-2xl border-2 border-[#ddd8eb] overflow-hidden">
|
||||
<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-[#f1eef9] border-b-2 border-[#ddd8eb]">
|
||||
<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-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<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-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<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-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<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-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<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-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<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-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="px-6 py-4 text-left text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Payment Method
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-[#ddd8eb]">
|
||||
<tbody className="divide-y divide-var(--neutral-800)">
|
||||
{filteredDonations.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="6" className="px-6 py-12 text-center">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<Heart className="h-12 w-12 text-[#ddd8eb]" />
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Heart className="h-12 w-12 text-var(--neutral-800)" />
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{donations.length === 0 ? 'No donations yet' : 'No donations match your filters'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -401,13 +401,13 @@ const AdminDonations = () => {
|
||||
</tr>
|
||||
) : (
|
||||
filteredDonations.map((donation) => (
|
||||
<tr key={donation.id} className="hover:bg-[#f9f5ff] transition-colors">
|
||||
<tr key={donation.id} className="hover:bg-var(--lavender-400) transition-colors">
|
||||
<td className="px-6 py-4">
|
||||
<div>
|
||||
<p className="font-medium text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="font-medium text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{donation.donor_name || 'Anonymous'}
|
||||
</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{donation.donor_email || 'No email'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -421,7 +421,7 @@ const AdminDonations = () => {
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<p className="font-semibold text-[#422268] text-lg" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="font-semibold text-var(--purple-ink) text-lg" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{donation.amount}
|
||||
</p>
|
||||
</td>
|
||||
@@ -431,7 +431,7 @@ const AdminDonations = () => {
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2 text-[#664fa3]">
|
||||
<div className="flex items-center gap-2 text-var(--purple-lavender)">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{formatDate(donation.created_at)}
|
||||
@@ -439,7 +439,7 @@ const AdminDonations = () => {
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{donation.payment_method || 'N/A'}
|
||||
</p>
|
||||
</td>
|
||||
@@ -453,18 +453,18 @@ const AdminDonations = () => {
|
||||
|
||||
{/* This Month Summary */}
|
||||
{stats.this_month_count > 0 && (
|
||||
<Card className="p-6 bg-gradient-to-r from-[#f9f5ff] to-[#f1eef9] rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-gradient-to-r from-var(--lavender-400) to-var(--lavender-300) rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
This Month's Donations
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-2xl font-bold text-var(--purple-ink) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.this_month_count} donations • {stats.this_month_amount}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-4 bg-white rounded-full shadow-sm">
|
||||
<Heart className="h-8 w-8 text-[#ff9e77]" />
|
||||
<div className="p-4 bg-background rounded-full shadow-sm">
|
||||
<Heart className="h-8 w-8 text-var(--orange-light)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -213,7 +213,7 @@ const AdminEventAttendance = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-[#664fa3]">Loading event data...</div>
|
||||
<div className="text-var(--purple-lavender)">Loading event data...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -221,8 +221,8 @@ const AdminEventAttendance = () => {
|
||||
if (!event) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-[#664fa3] mb-4">Event not found</p>
|
||||
<Button onClick={() => navigate('/admin/events')} className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl">
|
||||
<p className="text-var(--purple-lavender) mb-4">Event not found</p>
|
||||
<Button onClick={() => navigate('/admin/events')} className="bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white rounded-xl">
|
||||
Back to Events
|
||||
</Button>
|
||||
</div>
|
||||
@@ -237,24 +237,24 @@ const AdminEventAttendance = () => {
|
||||
<Button
|
||||
onClick={() => navigate('/admin/events')}
|
||||
variant="outline"
|
||||
className="border-[#ddd8eb] text-[#664fa3] rounded-xl"
|
||||
className="border-var(--neutral-800) text-var(--purple-lavender) rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back to Events
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Event Attendance
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage RSVPs and track attendance for this event
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={exportToCSV}
|
||||
className="bg-[#81B29A] hover:bg-[#6a9a83] text-white rounded-xl"
|
||||
className="bg-var(--green-light) hover:bg-var(--green-eucalyptus) text-white rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
@@ -263,13 +263,13 @@ const AdminEventAttendance = () => {
|
||||
</div>
|
||||
|
||||
{/* Event Details Card */}
|
||||
<Card className="p-6 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-6 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h2 className="text-2xl font-semibold text-var(--purple-ink) mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{event.title}
|
||||
</h2>
|
||||
<div className="flex flex-wrap gap-4 text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="flex flex-wrap gap-4 text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>{moment(event.start_at).format('MMMM D, YYYY [at] h:mm A')}</span>
|
||||
@@ -282,7 +282,7 @@ const AdminEventAttendance = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Badge className={`${event.published ? 'bg-[#81B29A]' : 'bg-[#ddd8eb]'} text-white px-3 py-1`}>
|
||||
<Badge className={`${event.published ? 'bg-var(--green-light)' : 'bg-var(--neutral-800)'} text-white px-3 py-1`}>
|
||||
{event.published ? 'Published' : 'Draft'}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -290,70 +290,69 @@ const AdminEventAttendance = () => {
|
||||
|
||||
{/* Statistics Cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
|
||||
<Card className="p-4 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-4 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-center gap-3">
|
||||
<Users className="h-8 w-8 text-[#664fa3]" />
|
||||
<Users className="h-8 w-8 text-var(--purple-lavender)" />
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total RSVPs</p>
|
||||
<p className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.total}</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total RSVPs</p>
|
||||
<p className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.total}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-4 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-center gap-3">
|
||||
<UserCheck className="h-8 w-8 text-[#81B29A]" />
|
||||
<UserCheck className="h-8 w-8 text-var(--green-light)" />
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Yes</p>
|
||||
<p className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.yesCount}</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Yes</p>
|
||||
<p className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.yesCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-4 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-center gap-3">
|
||||
<UserX className="h-8 w-8 text-[#E07A5F]" />
|
||||
<UserX className="h-8 w-8 text-var(--orange-soft)" />
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>No</p>
|
||||
<p className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.noCount}</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>No</p>
|
||||
<p className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.noCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-4 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-center gap-3">
|
||||
<HelpCircle className="h-8 w-8 text-[#F2CC8F]" />
|
||||
<HelpCircle className="h-8 w-8 text-var(--gold-warm)" />
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Maybe</p>
|
||||
<p className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.maybeCount}</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Maybe</p>
|
||||
<p className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.maybeCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-4 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-4 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-center gap-3">
|
||||
<Check className="h-8 w-8 text-[#664fa3]" />
|
||||
<Check className="h-8 w-8 text-var(--purple-lavender)" />
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Attended</p>
|
||||
<p className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.attendedCount}</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Attended</p>
|
||||
<p className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>{stats.attendedCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters and Actions */}
|
||||
<Card className="p-6 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-6 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="space-y-4">
|
||||
{/* Tab Filters */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button
|
||||
onClick={() => setActiveTab('all')}
|
||||
variant={activeTab === 'all' ? 'default' : 'outline'}
|
||||
className={`rounded-xl ${
|
||||
activeTab === 'all'
|
||||
? 'bg-[#664fa3] hover:bg-[#422268] text-white'
|
||||
: 'border-[#ddd8eb] text-[#664fa3] hover:bg-[#F8F7FB]'
|
||||
}`}
|
||||
className={`rounded-xl ${activeTab === 'all'
|
||||
? 'bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white'
|
||||
: 'border-var(--neutral-800) text-var(--purple-lavender) hover:bg-var(--lavender-500)'
|
||||
}`}
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
All ({stats.total})
|
||||
@@ -361,11 +360,10 @@ const AdminEventAttendance = () => {
|
||||
<Button
|
||||
onClick={() => setActiveTab('yes')}
|
||||
variant={activeTab === 'yes' ? 'default' : 'outline'}
|
||||
className={`rounded-xl ${
|
||||
activeTab === 'yes'
|
||||
? 'bg-[#81B29A] hover:bg-[#6a9a83] text-white'
|
||||
: 'border-[#ddd8eb] text-[#664fa3] hover:bg-[#F8F7FB]'
|
||||
}`}
|
||||
className={`rounded-xl ${activeTab === 'yes'
|
||||
? 'bg-var(--green-light) hover:bg-var(--green-eucalyptus) text-white'
|
||||
: 'border-var(--neutral-800) text-var(--purple-lavender) hover:bg-var(--lavender-500)'
|
||||
}`}
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
Yes ({stats.yesCount})
|
||||
@@ -373,11 +371,10 @@ const AdminEventAttendance = () => {
|
||||
<Button
|
||||
onClick={() => setActiveTab('no')}
|
||||
variant={activeTab === 'no' ? 'default' : 'outline'}
|
||||
className={`rounded-xl ${
|
||||
activeTab === 'no'
|
||||
? 'bg-[#E07A5F] hover:bg-[#d16b54] text-white'
|
||||
: 'border-[#ddd8eb] text-[#664fa3] hover:bg-[#F8F7FB]'
|
||||
}`}
|
||||
className={`rounded-xl ${activeTab === 'no'
|
||||
? 'bg-var(--orange-soft) hover:bg-var(--orange-rust) text-white'
|
||||
: 'border-var(--neutral-800) text-var(--purple-lavender) hover:bg-var(--lavender-500)'
|
||||
}`}
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
No ({stats.noCount})
|
||||
@@ -385,11 +382,10 @@ const AdminEventAttendance = () => {
|
||||
<Button
|
||||
onClick={() => setActiveTab('maybe')}
|
||||
variant={activeTab === 'maybe' ? 'default' : 'outline'}
|
||||
className={`rounded-xl ${
|
||||
activeTab === 'maybe'
|
||||
? 'bg-[#F2CC8F] hover:bg-[#e8bf7a] text-[#422268]'
|
||||
: 'border-[#ddd8eb] text-[#664fa3] hover:bg-[#F8F7FB]'
|
||||
}`}
|
||||
className={`rounded-xl ${activeTab === 'maybe'
|
||||
? 'bg-var(--gold-warm) hover:bg-var(--gold-soft) text-var(--purple-ink)'
|
||||
: 'border-var(--neutral-800) text-var(--purple-lavender) hover:bg-var(--lavender-500)'
|
||||
}`}
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
Maybe ({stats.maybeCount})
|
||||
@@ -399,26 +395,26 @@ const AdminEventAttendance = () => {
|
||||
{/* Search and Bulk Actions */}
|
||||
<div className="flex flex-wrap gap-3 items-center justify-between">
|
||||
<div className="flex-1 min-w-[200px] max-w-md relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-[#664fa3]" />
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-var(--purple-lavender)" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search by name or email..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 border-[#ddd8eb] rounded-xl"
|
||||
className="pl-10 border-var(--neutral-800) rounded-xl"
|
||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedRsvps.size > 0 && (
|
||||
<div className="flex gap-2">
|
||||
<Badge className="bg-[#664fa3] text-white px-3 py-1">
|
||||
<Badge className="bg-var(--purple-lavender) text-white px-3 py-1">
|
||||
{selectedRsvps.size} selected
|
||||
</Badge>
|
||||
<Button
|
||||
onClick={() => handleBulkAttendance(true)}
|
||||
disabled={saving}
|
||||
className="bg-[#81B29A] hover:bg-[#6a9a83] text-white rounded-xl"
|
||||
className="bg-var(--green-light) hover:bg-var(--green-eucalyptus) text-white rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Check className="h-4 w-4 mr-1" />
|
||||
@@ -427,7 +423,7 @@ const AdminEventAttendance = () => {
|
||||
<Button
|
||||
onClick={() => handleBulkAttendance(false)}
|
||||
disabled={saving}
|
||||
className="bg-[#E07A5F] hover:bg-[#d16b54] text-white rounded-xl"
|
||||
className="bg-var(--orange-soft) hover:bg-var(--orange-rust) text-white rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<X className="h-4 w-4 mr-1" />
|
||||
@@ -440,10 +436,10 @@ const AdminEventAttendance = () => {
|
||||
</Card>
|
||||
|
||||
{/* RSVP Table */}
|
||||
<Card className="bg-white border-[#ddd8eb] rounded-xl overflow-hidden">
|
||||
<Card className="bg-background border-var(--neutral-800) rounded-xl overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead className="bg-[#F8F7FB] border-b border-[#ddd8eb]">
|
||||
<thead className="bg-var(--lavender-500) border-b border-var(--neutral-800)">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left">
|
||||
<Checkbox
|
||||
@@ -451,19 +447,19 @@ const AdminEventAttendance = () => {
|
||||
onCheckedChange={handleSelectAll}
|
||||
/>
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Name
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Email
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
RSVP Status
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center text-sm font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="px-4 py-3 text-center text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Attendance
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="px-4 py-3 text-left text-sm font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Attended At
|
||||
</th>
|
||||
</tr>
|
||||
@@ -471,28 +467,27 @@ const AdminEventAttendance = () => {
|
||||
<tbody>
|
||||
{filteredRsvps.length > 0 ? (
|
||||
filteredRsvps.map((rsvp) => (
|
||||
<tr key={rsvp.user_id} className="border-b border-[#ddd8eb] hover:bg-[#F8F7FB] transition-colors">
|
||||
<tr key={rsvp.user_id} className="border-b border-var(--neutral-800) hover:bg-var(--lavender-500) transition-colors">
|
||||
<td className="px-4 py-3">
|
||||
<Checkbox
|
||||
checked={selectedRsvps.has(rsvp.user_id)}
|
||||
onCheckedChange={() => handleSelectRsvp(rsvp.user_id)}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<td className="px-4 py-3 text-sm text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{rsvp.user_name}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<td className="px-4 py-3 text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{rsvp.user_email}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<Badge
|
||||
className={`${
|
||||
rsvp.rsvp_status === 'yes'
|
||||
? 'bg-[#81B29A]'
|
||||
: rsvp.rsvp_status === 'no'
|
||||
? 'bg-[#E07A5F]'
|
||||
: 'bg-[#F2CC8F] text-[#422268]'
|
||||
} text-white text-xs px-2 py-1`}
|
||||
className={`${rsvp.rsvp_status === 'yes'
|
||||
? 'bg-var(--green-light)'
|
||||
: rsvp.rsvp_status === 'no'
|
||||
? 'bg-var(--orange-soft)'
|
||||
: 'bg-var(--gold-warm) text-var(--purple-ink)'
|
||||
} text-white text-xs px-2 py-1`}
|
||||
>
|
||||
{rsvp.rsvp_status.toUpperCase()}
|
||||
</Badge>
|
||||
@@ -503,7 +498,7 @@ const AdminEventAttendance = () => {
|
||||
onClick={() => handleIndividualAttendance(rsvp.user_id, false)}
|
||||
disabled={saving}
|
||||
size="sm"
|
||||
className="bg-[#81B29A] hover:bg-[#6a9a83] text-white rounded-lg min-w-[120px]"
|
||||
className="bg-var(--green-light) hover:bg-var(--green-eucalyptus) text-white rounded-lg min-w-[120px]"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Check className="h-3 w-3 mr-1" />
|
||||
@@ -515,7 +510,7 @@ const AdminEventAttendance = () => {
|
||||
disabled={saving}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="border-[#ddd8eb] text-[#664fa3] hover:bg-[#81B29A] hover:text-white hover:border-[#81B29A] rounded-lg min-w-[120px]"
|
||||
className="border-var(--neutral-800) text-var(--purple-lavender) hover:bg-var(--green-light) hover:text-white hover:border-var(--green-light) rounded-lg min-w-[120px]"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<X className="h-3 w-3 mr-1" />
|
||||
@@ -523,7 +518,7 @@ const AdminEventAttendance = () => {
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<td className="px-4 py-3 text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{rsvp.attended_at ? moment(rsvp.attended_at).format('MMM D, YYYY h:mm A') : '-'}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -531,7 +526,7 @@ const AdminEventAttendance = () => {
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="6" className="px-4 py-12 text-center">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery ? 'No RSVPs match your search' : 'No RSVPs for this filter'}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
@@ -134,15 +134,15 @@ const AdminEvents = () => {
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex flex-col sm:flex-row gap-4 justify-between items-start sm:items-center">
|
||||
<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" }}>
|
||||
Event 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" }}>
|
||||
Create and manage community events.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{(hasPermission('events.create') || hasPermission('events.edit')) && (
|
||||
{(hasPermission('events.create') || hasPermission('events.edit')) && (
|
||||
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
@@ -150,7 +150,7 @@ const AdminEvents = () => {
|
||||
resetForm();
|
||||
setEditingEvent(null);
|
||||
}}
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full px-6"
|
||||
data-testid="create-event-button"
|
||||
>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
@@ -160,39 +160,39 @@ const AdminEvents = () => {
|
||||
|
||||
<DialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<DialogTitle className="text-2xl text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{editingEvent ? 'Edit Event' : 'Create New Event'}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4 mt-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="block text-sm font-medium text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Title *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
||||
required
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="block text-sm font-medium text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
rows={4}
|
||||
className="w-full border-2 border-[#ddd8eb] focus:border-[#664fa3] rounded-lg p-3"
|
||||
className="w-full border-2 border-var(--neutral-800) focus:border-var(--purple-lavender) rounded-lg p-3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="block text-sm font-medium text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Start Date & Time *
|
||||
</label>
|
||||
<Input
|
||||
@@ -200,12 +200,12 @@ const AdminEvents = () => {
|
||||
value={formData.start_at}
|
||||
onChange={(e) => setFormData({ ...formData, start_at: e.target.value })}
|
||||
required
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="block text-sm font-medium text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
End Date & Time *
|
||||
</label>
|
||||
<Input
|
||||
@@ -213,25 +213,25 @@ const AdminEvents = () => {
|
||||
value={formData.end_at}
|
||||
onChange={(e) => setFormData({ ...formData, end_at: e.target.value })}
|
||||
required
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="block text-sm font-medium text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Location *
|
||||
</label>
|
||||
<Input
|
||||
value={formData.location}
|
||||
onChange={(e) => setFormData({ ...formData, location: e.target.value })}
|
||||
required
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="block text-sm font-medium text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Capacity (optional)
|
||||
</label>
|
||||
<Input
|
||||
@@ -239,7 +239,7 @@ const AdminEvents = () => {
|
||||
value={formData.capacity}
|
||||
onChange={(e) => setFormData({ ...formData, capacity: e.target.value })}
|
||||
placeholder="Leave empty for unlimited"
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -249,9 +249,9 @@ const AdminEvents = () => {
|
||||
id="published"
|
||||
checked={formData.published}
|
||||
onChange={(e) => setFormData({ ...formData, published: e.target.checked })}
|
||||
className="w-4 h-4 text-[#664fa3] border-[#ddd8eb] rounded focus:ring-[#664fa3]"
|
||||
className="w-4 h-4 text-var(--purple-lavender) border-var(--neutral-800) rounded focus:ring-var(--purple-lavender)"
|
||||
/>
|
||||
<label htmlFor="published" className="text-sm font-medium text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label htmlFor="published" className="text-sm font-medium text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Publish event (make visible to members)
|
||||
</label>
|
||||
</div>
|
||||
@@ -267,7 +267,7 @@ const AdminEvents = () => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="flex-1 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
className="flex-1 bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full"
|
||||
>
|
||||
{editingEvent ? 'Update Event' : 'Create Event'}
|
||||
</Button>
|
||||
@@ -275,146 +275,145 @@ const AdminEvents = () => {
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Events List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading events...</p>
|
||||
</div>
|
||||
|
||||
{/* Events List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading events...</p>
|
||||
</div>
|
||||
) : events.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{events.map((event) => (
|
||||
<Card
|
||||
key={event.id}
|
||||
className="p-6 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-lg transition-all"
|
||||
data-testid={`event-card-${event.id}`}
|
||||
>
|
||||
{/* Event Header */}
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="bg-[#DDD8EB]/20 p-3 rounded-lg">
|
||||
<Calendar className="h-6 w-6 text-[#664fa3]" />
|
||||
</div>
|
||||
<Badge
|
||||
className={`${
|
||||
event.published
|
||||
? 'bg-[#81B29A] text-white'
|
||||
: 'bg-gray-400 text-white'
|
||||
) : events.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{events.map((event) => (
|
||||
<Card
|
||||
key={event.id}
|
||||
className="p-6 bg-background rounded-2xl border border-var(--neutral-800) hover:shadow-lg transition-all"
|
||||
data-testid={`event-card-${event.id}`}
|
||||
>
|
||||
{/* Event Header */}
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div className="bg-var(--neutral-800)/20 p-3 rounded-lg">
|
||||
<Calendar className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
<Badge
|
||||
className={`${event.published
|
||||
? 'bg-var(--green-light) text-white'
|
||||
: 'bg-gray-400 text-white'
|
||||
} px-3 py-1 rounded-full`}
|
||||
>
|
||||
{event.published ? 'Published' : 'Draft'}
|
||||
</Badge>
|
||||
>
|
||||
{event.published ? 'Published' : 'Draft'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Event Details */}
|
||||
<h3 className="text-xl font-semibold text-var(--purple-ink) mb-3" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{event.title}
|
||||
</h3>
|
||||
|
||||
{event.description && (
|
||||
<p className="text-var(--purple-lavender) mb-4 line-clamp-2 text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{event.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center gap-2 text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>
|
||||
{new Date(event.start_at).toLocaleDateString()} at{' '}
|
||||
{new Date(event.start_at).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Event Details */}
|
||||
<h3 className="text-xl font-semibold text-[#422268] mb-3" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{event.title}
|
||||
</h3>
|
||||
|
||||
{event.description && (
|
||||
<p className="text-[#664fa3] mb-4 line-clamp-2 text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{event.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center gap-2 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>
|
||||
{new Date(event.start_at).toLocaleDateString()} at{' '}
|
||||
{new Date(event.start_at).toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span className="truncate">{event.location}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Users className="h-4 w-4" />
|
||||
<span>{event.rsvp_count || 0} attending</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<MapPin className="h-4 w-4" />
|
||||
<span className="truncate">{event.location}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Users className="h-4 w-4" />
|
||||
<span>{event.rsvp_count || 0} attending</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="space-y-2 pt-4 border-t border-[#ddd8eb]">
|
||||
{/* Manage Attendance Button */}
|
||||
{/* Actions */}
|
||||
<div className="space-y-2 pt-4 border-t border-var(--neutral-800)">
|
||||
{/* Manage Attendance Button */}
|
||||
<Button
|
||||
onClick={() => navigate(`/admin/events/${event.id}/attendance`)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full border-var(--green-light) text-var(--green-light) hover:bg-var(--green-light) hover:text-white"
|
||||
data-testid={`mark-attendance-${event.id}`}
|
||||
>
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
Manage Attendance ({event.rsvp_count || 0} RSVPs)
|
||||
</Button>
|
||||
|
||||
{/* Other Actions */}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => navigate(`/admin/events/${event.id}/attendance`)}
|
||||
onClick={() => togglePublish(event)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full border-[#81B29A] text-[#81B29A] hover:bg-[#81B29A] hover:text-white"
|
||||
data-testid={`mark-attendance-${event.id}`}
|
||||
className="flex-1 border-var(--purple-lavender) text-var(--purple-lavender) hover:bg-var(--purple-lavender) hover:text-white"
|
||||
data-testid={`toggle-publish-${event.id}`}
|
||||
>
|
||||
<Users className="h-4 w-4 mr-2" />
|
||||
Manage Attendance ({event.rsvp_count || 0} RSVPs)
|
||||
{event.published ? (
|
||||
<>
|
||||
<EyeOff className="h-4 w-4 mr-1" />
|
||||
Unpublish
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
Publish
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleEdit(event)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-gray-400 text-gray-600 hover:bg-gray-400 hover:text-white"
|
||||
data-testid={`edit-event-${event.id}`}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDelete(event.id)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-red-500 text-red-500 hover:bg-red-500 hover:text-white"
|
||||
data-testid={`delete-event-${event.id}`}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
{/* Other Actions */}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => togglePublish(event)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white"
|
||||
data-testid={`toggle-publish-${event.id}`}
|
||||
>
|
||||
{event.published ? (
|
||||
<>
|
||||
<EyeOff className="h-4 w-4 mr-1" />
|
||||
Unpublish
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
Publish
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleEdit(event)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-gray-400 text-gray-600 hover:bg-gray-400 hover:text-white"
|
||||
data-testid={`edit-event-${event.id}`}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleDelete(event.id)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-red-500 text-red-500 hover:bg-red-500 hover:text-white"
|
||||
data-testid={`delete-event-${event.id}`}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Calendar 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" }}>
|
||||
No Events Yet
|
||||
</h3>
|
||||
<p className="text-[#664fa3] mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Create your first event to get started!
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => setIsCreateDialogOpen(true)}
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8"
|
||||
>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
Create Event
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Calendar 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 Events Yet
|
||||
</h3>
|
||||
<p className="text-var(--purple-lavender) mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Create your first event to get started!
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => setIsCreateDialogOpen(true)}
|
||||
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full px-8"
|
||||
>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
Create Event
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -147,7 +147,7 @@ const AdminFinancials = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="text-[#664fa3]">Loading financial reports...</p>
|
||||
<p className="text-var(--purple-lavender)">Loading financial reports...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -157,17 +157,17 @@ const AdminFinancials = () => {
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Financial Reports Management
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage annual financial reports
|
||||
</p>
|
||||
</div>
|
||||
{hasPermission('financials.create') && (
|
||||
<Button
|
||||
onClick={handleCreate}
|
||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||
className="bg-var(--purple-lavender) text-white hover:bg-var(--purple-muted) rounded-full flex items-center gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Report
|
||||
@@ -178,10 +178,10 @@ const AdminFinancials = () => {
|
||||
{/* Reports List */}
|
||||
{reports.length === 0 ? (
|
||||
<Card className="p-12 text-center">
|
||||
<TrendingUp className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||
<p className="text-[#664fa3] text-lg mb-4">No financial reports yet</p>
|
||||
<TrendingUp className="h-16 w-16 text-var(--neutral-800) mx-auto mb-4" />
|
||||
<p className="text-var(--purple-lavender) text-lg mb-4">No financial reports yet</p>
|
||||
{hasPermission('financials.create') && (
|
||||
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
||||
<Button onClick={handleCreate} className="bg-var(--purple-lavender) text-white">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create First Report
|
||||
</Button>
|
||||
@@ -192,23 +192,23 @@ const AdminFinancials = () => {
|
||||
{reports.map(report => (
|
||||
<Card key={report.id} className="p-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="bg-gradient-to-br from-[#664fa3] to-[#422268] p-4 rounded-xl text-white min-w-[100px] text-center">
|
||||
<div className="bg-gradient-to-br from-var(--purple-lavender) to-var(--purple-ink) p-4 rounded-xl text-white min-w-[100px] text-center">
|
||||
<DollarSign className="h-6 w-6 mx-auto mb-1" />
|
||||
<div className="text-2xl font-bold">{report.year}</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-2">
|
||||
<h3 className="text-lg font-semibold text-var(--purple-ink) mb-2">
|
||||
{report.title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="border-[#664fa3] text-[#664fa3]">
|
||||
<Badge variant="outline" className="border-var(--purple-lavender) text-var(--purple-lavender)">
|
||||
{report.document_type === 'google_drive' ? 'Google Drive' : report.document_type.toUpperCase()}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => window.open(report.document_url, '_blank')}
|
||||
className="text-[#664fa3] hover:text-[#533a82]"
|
||||
className="text-var(--purple-lavender) hover:text-var(--purple-muted)"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-1" />
|
||||
View
|
||||
@@ -222,7 +222,7 @@ const AdminFinancials = () => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(report)}
|
||||
className="border-[#664fa3] text-[#664fa3]"
|
||||
className="border-var(--purple-lavender) text-var(--purple-lavender)"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -313,12 +313,12 @@ const AdminFinancials = () => {
|
||||
required={!selectedReport}
|
||||
/>
|
||||
{uploadedFile && (
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Selected: {uploadedFile.name}
|
||||
</p>
|
||||
)}
|
||||
{selectedReport && !uploadedFile && (
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Current file will be kept if no new file is selected
|
||||
</p>
|
||||
)}
|
||||
@@ -333,7 +333,7 @@ const AdminFinancials = () => {
|
||||
placeholder="https://docs.google.com/... or https://example.com/file.pdf"
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Paste the shareable link to your document (Google Drive, Dropbox, PDF URL, etc.)
|
||||
</p>
|
||||
</div>
|
||||
@@ -350,7 +350,7 @@ const AdminFinancials = () => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-[#664fa3] text-white"
|
||||
className="bg-var(--purple-lavender) text-white"
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? 'Saving...' : selectedReport ? 'Update' : 'Create'}
|
||||
|
||||
@@ -153,23 +153,23 @@ const AdminGallery = () => {
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Event Gallery Management
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Upload and manage photos for event galleries
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Event Selection */}
|
||||
<Card className="p-6 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-6 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Label className="text-var(--purple-ink) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Select Event
|
||||
</Label>
|
||||
<Select value={selectedEvent || ''} onValueChange={setSelectedEvent}>
|
||||
<SelectTrigger className="border-[#ddd8eb] rounded-xl">
|
||||
<SelectTrigger className="border-var(--neutral-800) rounded-xl">
|
||||
<SelectValue placeholder="Choose an event..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -184,19 +184,19 @@ const AdminGallery = () => {
|
||||
|
||||
{/* Empty State Message */}
|
||||
{events.length === 0 && (
|
||||
<div className="mt-4 p-4 bg-[#f1eef9] border-2 border-[#DDD8EB] rounded-xl">
|
||||
<div className="mt-4 p-4 bg-var(--lavender-300) border-2 border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="h-5 w-5 text-[#664fa3] flex-shrink-0 mt-0.5" />
|
||||
<AlertCircle className="h-5 w-5 text-var(--purple-lavender) flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h4 className="text-sm font-semibold text-var(--purple-ink) mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
No Events Available
|
||||
</h4>
|
||||
<p className="text-sm text-[#664fa3] mb-3" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mb-3" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
You need to create an event before uploading gallery images. Events help organize photos by occasion.
|
||||
</p>
|
||||
<Link to="/admin/events">
|
||||
<Button
|
||||
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl text-sm"
|
||||
className="bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white rounded-xl text-sm"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Calendar className="h-4 w-4 mr-2" />
|
||||
@@ -221,7 +221,7 @@ const AdminGallery = () => {
|
||||
<Button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploading}
|
||||
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl"
|
||||
className="bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
{uploading ? (
|
||||
@@ -236,7 +236,7 @@ const AdminGallery = () => {
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<p className="text-sm text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
You can select multiple images. Max {formatFileSize(maxFileSize)} per image.
|
||||
</p>
|
||||
</div>
|
||||
@@ -246,12 +246,12 @@ const AdminGallery = () => {
|
||||
|
||||
{/* Gallery Grid */}
|
||||
{selectedEvent && (
|
||||
<Card className="p-6 bg-white border-[#ddd8eb] rounded-xl">
|
||||
<Card className="p-6 bg-background border-var(--neutral-800) rounded-xl">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h2 className="text-xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Gallery Images
|
||||
</h2>
|
||||
<Badge className="bg-[#664fa3] text-white px-3 py-1">
|
||||
<Badge className="bg-var(--purple-lavender) text-white px-3 py-1">
|
||||
{galleryImages.length} {galleryImages.length === 1 ? 'image' : 'images'}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -260,7 +260,7 @@ const AdminGallery = () => {
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{galleryImages.map((image) => (
|
||||
<div key={image.id} className="relative group">
|
||||
<div className="aspect-square rounded-xl overflow-hidden bg-[#F8F7FB]">
|
||||
<div className="aspect-square rounded-xl overflow-hidden bg-var(--lavender-500)">
|
||||
<img
|
||||
src={image.image_url}
|
||||
alt={image.caption || 'Gallery image'}
|
||||
@@ -275,7 +275,7 @@ const AdminGallery = () => {
|
||||
<Button
|
||||
onClick={() => openEditCaption(image)}
|
||||
size="sm"
|
||||
className="bg-white/90 hover:bg-white text-[#422268] rounded-lg"
|
||||
className="bg-background/90 hover:bg-background text-var(--purple-ink) rounded-lg"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-1" />
|
||||
@@ -299,7 +299,7 @@ const AdminGallery = () => {
|
||||
{/* Caption Preview */}
|
||||
{image.caption && (
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-[#664fa3] line-clamp-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) line-clamp-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{image.caption}
|
||||
</p>
|
||||
</div>
|
||||
@@ -307,7 +307,7 @@ const AdminGallery = () => {
|
||||
|
||||
{/* File Size */}
|
||||
<div className="mt-1">
|
||||
<p className="text-xs text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-xs text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{formatFileSize(image.file_size_bytes)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -316,11 +316,11 @@ const AdminGallery = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-16">
|
||||
<ImageIcon className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<ImageIcon className="h-16 w-16 text-var(--neutral-800) mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-var(--purple-ink) mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
No Images Yet
|
||||
</h3>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Upload images to create a gallery for this event.
|
||||
</p>
|
||||
</div>
|
||||
@@ -330,16 +330,16 @@ const AdminGallery = () => {
|
||||
|
||||
{/* Edit Caption Dialog */}
|
||||
<Dialog open={!!editingCaption} onOpenChange={() => setEditingCaption(null)}>
|
||||
<DialogContent className="bg-white sm:max-w-[600px]">
|
||||
<DialogContent className="bg-background sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<DialogTitle className="text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Edit Image Caption
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{editingCaption && (
|
||||
<div className="space-y-4">
|
||||
<div className="aspect-video rounded-xl overflow-hidden bg-[#F8F7FB]">
|
||||
<div className="aspect-video rounded-xl overflow-hidden bg-var(--lavender-500)">
|
||||
<img
|
||||
src={editingCaption.image_url}
|
||||
alt="Preview"
|
||||
@@ -348,7 +348,7 @@ const AdminGallery = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<Label className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Caption
|
||||
</Label>
|
||||
<Textarea
|
||||
@@ -356,7 +356,7 @@ const AdminGallery = () => {
|
||||
onChange={(e) => setNewCaption(e.target.value)}
|
||||
placeholder="Add a caption for this image..."
|
||||
rows={3}
|
||||
className="border-[#ddd8eb] rounded-xl mt-2"
|
||||
className="border-var(--neutral-800) rounded-xl mt-2"
|
||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||
/>
|
||||
</div>
|
||||
@@ -367,14 +367,14 @@ const AdminGallery = () => {
|
||||
<Button
|
||||
onClick={() => setEditingCaption(null)}
|
||||
variant="outline"
|
||||
className="border-[#ddd8eb] text-[#664fa3] rounded-xl"
|
||||
className="border-var(--neutral-800) text-var(--purple-lavender) rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleUpdateCaption}
|
||||
className="bg-[#664fa3] hover:bg-[#422268] text-white rounded-xl"
|
||||
className="bg-var(--purple-lavender) hover:bg-var(--purple-ink) text-white rounded-xl"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
Save Caption
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -175,7 +175,7 @@ const AdminNewsletters = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="text-[#664fa3]">Loading newsletters...</p>
|
||||
<p className="text-var(--purple-lavender)">Loading newsletters...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -185,17 +185,17 @@ const AdminNewsletters = () => {
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Newsletter Management
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Create and manage newsletter archive
|
||||
</p>
|
||||
</div>
|
||||
{hasPermission('newsletters.create') && (
|
||||
<Button
|
||||
onClick={handleCreate}
|
||||
className="bg-[#664fa3] text-white hover:bg-[#533a82] rounded-full flex items-center gap-2"
|
||||
className="bg-var(--purple-lavender) text-white hover:bg-var(--purple-muted) rounded-full flex items-center gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Newsletter
|
||||
@@ -206,10 +206,10 @@ const AdminNewsletters = () => {
|
||||
{/* Newsletters List */}
|
||||
{newsletters.length === 0 ? (
|
||||
<Card className="p-12 text-center">
|
||||
<FileText className="h-16 w-16 text-[#ddd8eb] mx-auto mb-4" />
|
||||
<p className="text-[#664fa3] text-lg mb-4">No newsletters yet</p>
|
||||
<FileText className="h-16 w-16 text-var(--neutral-800) mx-auto mb-4" />
|
||||
<p className="text-var(--purple-lavender) text-lg mb-4">No newsletters yet</p>
|
||||
{hasPermission('newsletters.create') && (
|
||||
<Button onClick={handleCreate} className="bg-[#664fa3] text-white">
|
||||
<Button onClick={handleCreate} className="bg-var(--purple-lavender) text-white">
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create First Newsletter
|
||||
</Button>
|
||||
@@ -219,7 +219,7 @@ const AdminNewsletters = () => {
|
||||
<div className="space-y-6">
|
||||
{sortedYears.map(year => (
|
||||
<div key={year}>
|
||||
<h2 className="text-xl font-semibold text-[#422268] mb-4 flex items-center gap-2">
|
||||
<h2 className="text-xl font-semibold text-var(--purple-ink) mb-4 flex items-center gap-2">
|
||||
<Calendar className="h-5 w-5" />
|
||||
{year}
|
||||
</h2>
|
||||
@@ -228,24 +228,24 @@ const AdminNewsletters = () => {
|
||||
<Card key={newsletter.id} className="p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-2">
|
||||
<h3 className="text-lg font-semibold text-var(--purple-ink) mb-2">
|
||||
{newsletter.title}
|
||||
</h3>
|
||||
{newsletter.description && (
|
||||
<p className="text-[#664fa3] mb-3">{newsletter.description}</p>
|
||||
<p className="text-var(--purple-lavender) mb-3">{newsletter.description}</p>
|
||||
)}
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge className="bg-[#DDD8EB] text-[#422268]">
|
||||
<Badge className="bg-var(--neutral-800) text-var(--purple-ink)">
|
||||
{formatDate(newsletter.published_date)}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-[#664fa3] text-[#664fa3]">
|
||||
<Badge variant="outline" className="border-var(--purple-lavender) text-var(--purple-lavender)">
|
||||
{newsletter.document_type === 'upload' ? 'PDF Upload' : 'Link'}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => window.open(newsletter.document_url, '_blank')}
|
||||
className="text-[#664fa3] hover:text-[#533a82]"
|
||||
className="text-var(--purple-lavender) hover:text-var(--purple-muted)"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4 mr-1" />
|
||||
View
|
||||
@@ -259,7 +259,7 @@ const AdminNewsletters = () => {
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleEdit(newsletter)}
|
||||
className="border-[#664fa3] text-[#664fa3]"
|
||||
className="border-var(--purple-lavender) text-var(--purple-lavender)"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -361,12 +361,12 @@ const AdminNewsletters = () => {
|
||||
required={!selectedNewsletter}
|
||||
/>
|
||||
{uploadedFile && (
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Selected: {uploadedFile.name}
|
||||
</p>
|
||||
)}
|
||||
{selectedNewsletter && !uploadedFile && (
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Current file will be kept if no new file is selected
|
||||
</p>
|
||||
)}
|
||||
@@ -381,7 +381,7 @@ const AdminNewsletters = () => {
|
||||
placeholder="https://docs.google.com/document/d/... or https://example.com/file.pdf"
|
||||
required
|
||||
/>
|
||||
<p className="text-sm text-[#664fa3] mt-1">
|
||||
<p className="text-sm text-var(--purple-lavender) mt-1">
|
||||
Paste the shareable link to your document (Google Docs, Dropbox, PDF URL, etc.)
|
||||
</p>
|
||||
</div>
|
||||
@@ -398,7 +398,7 @@ const AdminNewsletters = () => {
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
className="bg-[#664fa3] text-white"
|
||||
className="bg-var(--purple-lavender) text-white"
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? 'Saving...' : selectedNewsletter ? 'Update' : 'Create'}
|
||||
|
||||
@@ -187,8 +187,8 @@ const AdminPermissions = () => {
|
||||
|
||||
const getRoleBadge = (role) => {
|
||||
const config = {
|
||||
admin: { label: 'Admin', color: 'bg-[#81B29A]', icon: Shield },
|
||||
member: { label: 'Member', color: 'bg-[#664fa3]', icon: Shield },
|
||||
admin: { label: 'Admin', color: 'bg-var(--green-light)', icon: Shield },
|
||||
member: { label: 'Member', color: 'bg-var(--purple-lavender)', icon: Shield },
|
||||
guest: { label: 'Guest', color: 'bg-gray-400', icon: Shield }
|
||||
};
|
||||
|
||||
@@ -206,7 +206,7 @@ const AdminPermissions = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Loading permissions...
|
||||
</p>
|
||||
</div>
|
||||
@@ -217,10 +217,10 @@ const AdminPermissions = () => {
|
||||
return (
|
||||
<div className="text-center py-20">
|
||||
<Lock className="h-20 w-20 text-red-500 mx-auto mb-6" />
|
||||
<h2 className="text-3xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h2 className="text-3xl font-semibold text-var(--purple-ink) mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Access Denied
|
||||
</h2>
|
||||
<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" }}>
|
||||
You don't have permission to manage role permissions.
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mt-2">
|
||||
@@ -233,10 +233,10 @@ const AdminPermissions = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<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" }}>
|
||||
Permission 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" }}>
|
||||
Configure granular permissions for each role. Superadmin always has all permissions.
|
||||
</p>
|
||||
</div>
|
||||
@@ -259,27 +259,27 @@ const AdminPermissions = () => {
|
||||
<TabsContent key={role} value={role}>
|
||||
{/* Stats */}
|
||||
<div className="grid md:grid-cols-3 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" }}>
|
||||
<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 Permissions
|
||||
</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{permissions.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" }}>
|
||||
<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" }}>
|
||||
Assigned
|
||||
</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{selectedPermissions[role].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" }}>
|
||||
<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" }}>
|
||||
Modules
|
||||
</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{Object.keys(groupedPermissions).length}
|
||||
</p>
|
||||
</Card>
|
||||
@@ -288,10 +288,10 @@ const AdminPermissions = () => {
|
||||
{/* Permissions by Module */}
|
||||
<div className="space-y-4">
|
||||
{Object.entries(groupedPermissions).map(([module, perms]) => (
|
||||
<Card key={module} className="bg-white rounded-2xl border border-[#ddd8eb] overflow-hidden">
|
||||
<Card key={module} className="bg-background rounded-2xl border border-var(--neutral-800) overflow-hidden">
|
||||
{/* Module Header */}
|
||||
<div
|
||||
className="p-6 bg-gradient-to-r from-[#DDD8EB] to-white cursor-pointer hover:from-[#C5BFD9] transition-colors"
|
||||
className="p-6 bg-gradient-to-r from-var(--neutral-800) to-white cursor-pointer hover:from-var(--neutral-600) transition-colors"
|
||||
onClick={() => toggleModuleExpansion(module)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -300,21 +300,21 @@ const AdminPermissions = () => {
|
||||
checked={isModuleFullySelected(role, module)}
|
||||
onCheckedChange={() => toggleModule(role, module)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="h-6 w-6 border-2 border-[#664fa3] data-[state=checked]:bg-[#664fa3]"
|
||||
className="h-6 w-6 border-2 border-var(--purple-lavender) data-[state=checked]:bg-var(--purple-lavender)"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-[#422268] capitalize" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h3 className="text-xl font-semibold text-var(--purple-ink) capitalize" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{module}
|
||||
</h3>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{getModuleProgress(role, module)} permissions
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{expandedModules[module] ? (
|
||||
<ChevronUp className="h-6 w-6 text-[#664fa3]" />
|
||||
<ChevronUp className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
) : (
|
||||
<ChevronDown className="h-6 w-6 text-[#664fa3]" />
|
||||
<ChevronDown className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -326,18 +326,18 @@ const AdminPermissions = () => {
|
||||
{perms.map(perm => (
|
||||
<div
|
||||
key={perm.code}
|
||||
className="flex items-start gap-4 p-4 rounded-xl hover:bg-[#F9F8FB] transition-colors border border-transparent hover:border-[#ddd8eb]"
|
||||
className="flex items-start gap-4 p-4 rounded-xl hover:bg-var(--lavender-700) transition-colors border border-transparent hover:border-var(--neutral-800)"
|
||||
>
|
||||
<Checkbox
|
||||
checked={selectedPermissions[role].includes(perm.code)}
|
||||
onCheckedChange={() => togglePermission(role, perm.code)}
|
||||
className="mt-1 h-5 w-5 border-2 border-[#664fa3] data-[state=checked]:bg-[#664fa3]"
|
||||
className="mt-1 h-5 w-5 border-2 border-var(--purple-lavender) data-[state=checked]:bg-var(--purple-lavender)"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="font-semibold text-var(--purple-ink) mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{perm.name}
|
||||
</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{perm.description}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1 font-mono">
|
||||
@@ -357,7 +357,7 @@ const AdminPermissions = () => {
|
||||
</Tabs>
|
||||
|
||||
{/* Superadmin Note */}
|
||||
<Card className="p-6 bg-gradient-to-r from-[#664fa3] to-[#422268] rounded-2xl border-none mb-8">
|
||||
<Card className="p-6 bg-gradient-to-r from-var(--purple-lavender) to-var(--purple-ink) rounded-2xl border-none mb-8">
|
||||
<div className="flex items-start gap-4">
|
||||
<Lock className="h-6 w-6 text-white flex-shrink-0 mt-1" />
|
||||
<div className="text-white">
|
||||
@@ -377,7 +377,7 @@ const AdminPermissions = () => {
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="h-14 px-8 bg-[#81B29A] hover:bg-[#6DA085] text-white rounded-full shadow-lg text-lg font-semibold"
|
||||
className="h-14 px-8 bg-var(--green-light) hover:bg-var(--green-fern) text-white rounded-full shadow-lg text-lg font-semibold"
|
||||
>
|
||||
<Save className="h-5 w-5 mr-2" />
|
||||
{saving ? 'Saving...' : 'Save Changes'}
|
||||
@@ -389,10 +389,10 @@ const AdminPermissions = () => {
|
||||
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
||||
<AlertDialogContent className="rounded-2xl">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="text-2xl text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<AlertDialogTitle className="text-2xl text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Confirm Permission Changes
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<AlertDialogDescription className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Are you sure you want to update permissions for <span className="font-semibold capitalize">{selectedRole}</span>?
|
||||
This will immediately affect all users with this role.
|
||||
</AlertDialogDescription>
|
||||
@@ -401,7 +401,7 @@ const AdminPermissions = () => {
|
||||
<AlertDialogCancel className="rounded-xl">Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={confirmSave}
|
||||
className="rounded-xl bg-[#81B29A] hover:bg-[#6DA085] text-white"
|
||||
className="rounded-xl bg-var(--green-light) hover:bg-var(--green-fern) text-white"
|
||||
>
|
||||
Confirm
|
||||
</AlertDialogAction>
|
||||
|
||||
@@ -131,17 +131,17 @@ const AdminPlans = () => {
|
||||
<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" }}>
|
||||
Subscription Plans
|
||||
</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 membership plans and pricing.
|
||||
</p>
|
||||
</div>
|
||||
{hasPermission('subscriptions.plans') && (
|
||||
<Button
|
||||
onClick={handleCreatePlan}
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full px-6"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create Plan
|
||||
@@ -152,27 +152,27 @@ const AdminPlans = () => {
|
||||
|
||||
{/* 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 Plans</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 Plans</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{plans.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 Plans</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 Plans</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{plans.filter(p => p.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" }}>Total Subscribers</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 Subscribers</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{plans.reduce((sum, p) => sum + (p.subscriber_count || 0), 0)}
|
||||
</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" }}>Revenue (Annual Est.)</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" }}>Revenue (Annual Est.)</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(
|
||||
plans.reduce((sum, p) => {
|
||||
const annualPrice = p.billing_cycle === 'yearly'
|
||||
@@ -186,19 +186,19 @@ const AdminPlans = () => {
|
||||
</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 plans..."
|
||||
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)"
|
||||
/>
|
||||
</div>
|
||||
<Select value={activeFilter} onValueChange={setActiveFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="Filter by status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -213,56 +213,54 @@ const AdminPlans = () => {
|
||||
{/* Plans Grid */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading plans...</p>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading plans...</p>
|
||||
</div>
|
||||
) : filteredPlans.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredPlans.map((plan) => (
|
||||
<Card
|
||||
key={plan.id}
|
||||
className={`p-6 bg-white rounded-2xl border-2 transition-all hover:shadow-lg ${
|
||||
plan.active
|
||||
? 'border-[#ddd8eb] hover:border-[#664fa3]'
|
||||
: 'border-gray-400 opacity-60'
|
||||
}`}
|
||||
className={`p-6 bg-background rounded-2xl border-2 transition-all hover:shadow-lg ${plan.active
|
||||
? 'border-var(--neutral-800) hover:border-var(--purple-lavender)'
|
||||
: 'border-gray-400 opacity-60'
|
||||
}`}
|
||||
>
|
||||
{/* Header with badges */}
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
<Badge
|
||||
className={`${
|
||||
plan.active
|
||||
? 'bg-[#81B29A] text-white'
|
||||
: 'bg-gray-400 text-white'
|
||||
}`}
|
||||
className={`${plan.active
|
||||
? 'bg-var(--green-light) text-white'
|
||||
: 'bg-gray-400 text-white'
|
||||
}`}
|
||||
>
|
||||
{plan.active ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
{plan.subscriber_count > 0 && (
|
||||
<Badge className="bg-[#DDD8EB] text-[#422268]">
|
||||
<Badge className="bg-var(--neutral-800) text-var(--purple-ink)">
|
||||
<Users className="h-3 w-3 mr-1" />
|
||||
{plan.subscriber_count}
|
||||
</Badge>
|
||||
)}
|
||||
{plan.custom_cycle_enabled && (
|
||||
<Badge className="bg-[#664fa3] text-white">
|
||||
<Badge className="bg-var(--purple-lavender) text-white">
|
||||
Custom Dates
|
||||
</Badge>
|
||||
)}
|
||||
{plan.allow_donation && (
|
||||
<Badge className="bg-[#ff9e77] text-white">
|
||||
<Badge className="bg-var(--orange-light) text-white">
|
||||
Donations Enabled
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Plan Name */}
|
||||
<h3 className="text-2xl font-semibold text-[#422268] mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h3 className="text-2xl font-semibold text-var(--purple-ink) mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{plan.name}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
{plan.description && (
|
||||
<p className="text-sm text-[#664fa3] mb-4 line-clamp-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mb-4 line-clamp-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{plan.description}
|
||||
</p>
|
||||
)}
|
||||
@@ -270,20 +268,20 @@ const AdminPlans = () => {
|
||||
{/* Price */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<div className="text-3xl font-bold text-[#ff9e77]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<div className="text-3xl font-bold text-var(--orange-light)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(plan.minimum_price_cents || plan.price_cents)}
|
||||
</div>
|
||||
{plan.suggested_price_cents && plan.suggested_price_cents > plan.minimum_price_cents && (
|
||||
<div className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="text-lg text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
(Suggested: {formatPrice(plan.suggested_price_cents)})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{getBillingCycleLabel(plan.billing_cycle)}
|
||||
</p>
|
||||
{plan.custom_cycle_enabled && (
|
||||
<p className="text-xs text-[#664fa3] font-mono mt-1">
|
||||
<p className="text-xs text-var(--purple-lavender) font-mono mt-1">
|
||||
{formatCustomCycleDates(plan)}
|
||||
</p>
|
||||
)}
|
||||
@@ -291,12 +289,12 @@ const AdminPlans = () => {
|
||||
|
||||
{/* Actions */}
|
||||
{hasPermission('subscriptions.plans') && (
|
||||
<div className="flex flex-col sm:flex-row gap-2 pt-4 border-t border-[#ddd8eb]">
|
||||
<div className="flex flex-col sm:flex-row gap-2 pt-4 border-t border-var(--neutral-800)">
|
||||
<Button
|
||||
onClick={() => handleEditPlan(plan)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white rounded-full"
|
||||
className="flex-1 border-var(--purple-lavender) text-var(--purple-lavender) hover:bg-var(--purple-lavender) hover:text-white rounded-full"
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-1" />
|
||||
Edit
|
||||
@@ -316,7 +314,7 @@ const AdminPlans = () => {
|
||||
|
||||
{/* Warning for plans with subscribers */}
|
||||
{plan.subscriber_count > 0 && (
|
||||
<p className="text-xs text-[#664fa3] mt-2 text-center" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-xs text-var(--purple-lavender) mt-2 text-center" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Cannot delete plan with active subscribers
|
||||
</p>
|
||||
)}
|
||||
@@ -325,11 +323,11 @@ const AdminPlans = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<CreditCard 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" }}>
|
||||
<CreditCard 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 Plans Found
|
||||
</h3>
|
||||
<p className="text-[#664fa3] mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery || activeFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'Create your first subscription plan to get started'}
|
||||
@@ -337,7 +335,7 @@ const AdminPlans = () => {
|
||||
{!searchQuery && activeFilter === 'all' && hasPermission('subscriptions.plans') && (
|
||||
<Button
|
||||
onClick={handleCreatePlan}
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8"
|
||||
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background rounded-full px-8"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create First Plan
|
||||
@@ -357,11 +355,11 @@ const AdminPlans = () => {
|
||||
{/* Delete Confirmation Dialog */}
|
||||
{deleteDialogOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<Card className="p-6 sm:p-8 bg-white rounded-2xl max-w-md w-full">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Card className="p-6 sm:p-8 bg-background rounded-2xl max-w-md w-full">
|
||||
<h2 className="text-xl sm:text-2xl font-semibold text-var(--purple-ink) mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Delete Plan
|
||||
</h2>
|
||||
<p className="text-sm sm:text-base text-[#664fa3] mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm sm:text-base text-var(--purple-lavender) mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Are you sure you want to delete "{planToDelete?.name}"? This action
|
||||
will deactivate the plan and it won't be available for new subscriptions.
|
||||
</p>
|
||||
|
||||
@@ -99,9 +99,9 @@ const AdminStaff = () => {
|
||||
|
||||
const getRoleBadge = (role) => {
|
||||
const config = {
|
||||
superadmin: { label: 'Superadmin', className: 'bg-[#664fa3] text-white' },
|
||||
admin: { label: 'Admin', className: 'bg-[#81B29A] text-white' },
|
||||
moderator: { label: 'Moderator', className: 'bg-[#DDD8EB] text-[#422268]' },
|
||||
superadmin: { label: 'Superadmin', className: 'bg-var(--purple-lavender) text-white' },
|
||||
admin: { label: 'Admin', className: 'bg-var(--green-light) text-white' },
|
||||
moderator: { label: 'Moderator', className: 'bg-var(--neutral-800) text-var(--purple-ink)' },
|
||||
staff: { label: 'Staff', className: 'bg-gray-200 text-gray-700' },
|
||||
media: { label: 'Media', className: 'bg-gray-400 text-white' }
|
||||
};
|
||||
@@ -117,7 +117,7 @@ const AdminStaff = () => {
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
const config = {
|
||||
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 ' }
|
||||
};
|
||||
|
||||
@@ -134,10 +134,10 @@ const AdminStaff = () => {
|
||||
<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" }}>
|
||||
Staff 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 internal team members and their roles.
|
||||
</p>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@ const AdminStaff = () => {
|
||||
{hasPermission('users.create') && (
|
||||
<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 Staff
|
||||
@@ -154,7 +154,7 @@ const AdminStaff = () => {
|
||||
{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 Staff
|
||||
@@ -166,27 +166,27 @@ const AdminStaff = () => {
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid 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 Staff</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 Staff</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" }}>Admins</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" }}>Admins</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{users.filter(u => ['admin', 'superadmin'].includes(u.role)).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" }}>Moderators</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" }}>Moderators</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{users.filter(u => u.role === 'moderator').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>
|
||||
@@ -207,20 +207,20 @@ const AdminStaff = () => {
|
||||
|
||||
<TabsContent value="staff-list">
|
||||
{/* 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 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-staff-input"
|
||||
/>
|
||||
</div>
|
||||
<Select value={roleFilter} onValueChange={setRoleFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]" data-testid="role-filter-select">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-var(--neutral-800)" data-testid="role-filter-select">
|
||||
<SelectValue placeholder="Filter by role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -238,33 +238,33 @@ const AdminStaff = () => {
|
||||
{/* Staff List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading staff...</p>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading staff...</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={`staff-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>
|
||||
{getRoleBadge(user.role)}
|
||||
{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>
|
||||
@@ -280,7 +280,7 @@ const AdminStaff = () => {
|
||||
<Button
|
||||
onClick={() => navigate(`/admin/users/${user.id}`)}
|
||||
variant="outline"
|
||||
className="border-2 border-[#664fa3] text-[#664fa3] hover:bg-[#f1eef9] rounded-full px-4 py-2"
|
||||
className="border-2 border-var(--purple-lavender) text-var(--purple-lavender) hover:bg-var(--lavender-300) rounded-full px-4 py-2"
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Manage
|
||||
@@ -290,11 +290,10 @@ const AdminStaff = () => {
|
||||
<Button
|
||||
onClick={() => handleToggleStatus(user.id, user.status)}
|
||||
variant="outline"
|
||||
className={`border-2 rounded-full px-4 py-2 ${
|
||||
user.status === 'active'
|
||||
? 'border-orange-500 text-orange-600 hover:bg-orange-50'
|
||||
: 'border-green-500 text-green-600 hover:bg-green-50'
|
||||
}`}
|
||||
className={`border-2 rounded-full px-4 py-2 ${user.status === 'active'
|
||||
? 'border-orange-500 text-orange-600 hover:bg-orange-50'
|
||||
: 'border-green-500 text-green-600 hover:bg-green-50'
|
||||
}`}
|
||||
>
|
||||
{user.status === 'active' ? (
|
||||
<>
|
||||
@@ -327,11 +326,11 @@ const AdminStaff = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<UserCog 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" }}>
|
||||
<UserCog 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 Staff 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 || roleFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'No staff members yet'}
|
||||
|
||||
@@ -277,7 +277,7 @@ Proceed with activation?`;
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Loader2 className="h-12 w-12 animate-spin text-[#664fa3]" />
|
||||
<Loader2 className="h-12 w-12 animate-spin text-var(--purple-lavender)" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -286,93 +286,93 @@ Proceed with activation?`;
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Subscription Management
|
||||
</h1>
|
||||
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-var(--purple-lavender) mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
View and manage all member subscriptions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid md:grid-cols-4 gap-6">
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Total Subscriptions
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--purple-ink) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.total || 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
|
||||
<CreditCard className="h-6 w-6 text-[#664fa3]" />
|
||||
<div className="p-3 bg-var(--neutral-800)/20 rounded-full">
|
||||
<CreditCard className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Active Members
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#81B29A] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--green-light) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{stats.active || 0}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#81B29A]/10 rounded-full">
|
||||
<TrendingUp className="h-6 w-6 text-[#81B29A]" />
|
||||
<div className="p-3 bg-var(--green-light)/10 rounded-full">
|
||||
<TrendingUp className="h-6 w-6 text-var(--green-light)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Total Revenue
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--purple-ink) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(stats.total_revenue || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
|
||||
<DollarSign className="h-6 w-6 text-[#664fa3]" />
|
||||
<div className="p-3 bg-var(--neutral-800)/20 rounded-full">
|
||||
<DollarSign className="h-6 w-6 text-var(--purple-lavender)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Total Donations
|
||||
</p>
|
||||
<p className="text-3xl font-bold text-[#ff9e77] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-3xl font-bold text-var(--orange-light) mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(stats.total_donations || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-3 bg-[#ff9e77]/10 rounded-full">
|
||||
<Heart className="h-6 w-6 text-[#ff9e77]" />
|
||||
<div className="p-3 bg-var(--orange-light)/10 rounded-full">
|
||||
<Heart className="h-6 w-6 text-var(--orange-light)" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Search & Filter Bar */}
|
||||
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
|
||||
<Card className="p-6 bg-background rounded-2xl border-2 border-var(--neutral-800)">
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
{/* Search */}
|
||||
<div className="md:col-span-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Search className="absolute left-3 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-10 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="pl-10 rounded-xl border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -380,7 +380,7 @@ Proceed with activation?`;
|
||||
{/* Status Filter */}
|
||||
<div>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="rounded-xl border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="All Statuses" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -395,7 +395,7 @@ Proceed with activation?`;
|
||||
{/* Plan Filter */}
|
||||
<div>
|
||||
<Select value={planFilter} onValueChange={setPlanFilter}>
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="rounded-xl border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="All Plans" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -409,7 +409,7 @@ Proceed with activation?`;
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Showing {filteredSubscriptions.length} of {subscriptions.length} subscriptions
|
||||
</div>
|
||||
|
||||
@@ -419,26 +419,26 @@ Proceed with activation?`;
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
disabled={exporting}
|
||||
className="bg-[#81B29A] text-white hover:bg-[#6a9680] rounded-full px-6 py-2 flex items-center gap-2"
|
||||
className="bg-var(--green-light) text-white hover:bg-var(--green-soft) rounded-full px-6 py-2 flex items-center gap-2"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
{exporting ? 'Exporting...' : 'Export'}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56 bg-white rounded-xl border-2 border-[#ddd8eb] shadow-lg">
|
||||
<DropdownMenuContent align="end" className="w-56 bg-background rounded-xl border-2 border-var(--neutral-800) shadow-lg">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleExport('all')}
|
||||
className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3"
|
||||
className="cursor-pointer hover:bg-var(--lavender-300) rounded-lg p-3"
|
||||
>
|
||||
<FileDown className="h-4 w-4 mr-2 text-[#664fa3]" />
|
||||
<span className="text-[#422268]">Export All Subscriptions</span>
|
||||
<FileDown className="h-4 w-4 mr-2 text-var(--purple-lavender)" />
|
||||
<span className="text-var(--purple-ink)">Export All Subscriptions</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleExport('current')}
|
||||
className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3"
|
||||
className="cursor-pointer hover:bg-var(--lavender-300) rounded-lg p-3"
|
||||
>
|
||||
<FileDown className="h-4 w-4 mr-2 text-[#664fa3]" />
|
||||
<span className="text-[#422268]">Export Current View</span>
|
||||
<FileDown className="h-4 w-4 mr-2 text-var(--purple-lavender)" />
|
||||
<span className="text-var(--purple-ink)">Export Current View</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -447,20 +447,20 @@ Proceed with activation?`;
|
||||
</Card>
|
||||
|
||||
{/* Subscriptions Table */}
|
||||
<Card className="bg-white rounded-2xl border-2 border-[#ddd8eb] overflow-hidden">
|
||||
<Card className="bg-background rounded-2xl border-2 border-var(--neutral-800) overflow-hidden">
|
||||
{/* Mobile Card View */}
|
||||
<div className="md:hidden p-4 space-y-4">
|
||||
{filteredSubscriptions.length > 0 ? (
|
||||
filteredSubscriptions.map((sub) => (
|
||||
<Card key={sub.id} className="p-4 border border-[#ddd8eb] bg-[#f9f5ff]/30">
|
||||
<Card key={sub.id} className="p-4 border border-var(--neutral-800) bg-var(--lavender-400)/30">
|
||||
<div className="space-y-3">
|
||||
{/* Member Info */}
|
||||
<div className="flex justify-between items-start border-b border-[#ddd8eb] pb-3">
|
||||
<div className="flex justify-between items-start border-b border-var(--neutral-800) pb-3">
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{sub.user.first_name} {sub.user.last_name}
|
||||
</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{sub.user.email}
|
||||
</p>
|
||||
</div>
|
||||
@@ -470,13 +470,13 @@ Proceed with activation?`;
|
||||
{/* Plan & Period */}
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<p className="text-xs text-[#664fa3] mb-1">Plan</p>
|
||||
<p className="font-medium text-[#422268]">{sub.plan.name}</p>
|
||||
<p className="text-xs text-[#664fa3]">{sub.plan.billing_cycle}</p>
|
||||
<p className="text-xs text-var(--purple-lavender) mb-1">Plan</p>
|
||||
<p className="font-medium text-var(--purple-ink)">{sub.plan.name}</p>
|
||||
<p className="text-xs text-var(--purple-lavender)">{sub.plan.billing_cycle}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-[#664fa3] mb-1">Period</p>
|
||||
<p className="text-[#422268]">
|
||||
<p className="text-xs text-var(--purple-lavender) mb-1">Period</p>
|
||||
<p className="text-var(--purple-ink)">
|
||||
{new Date(sub.current_period_start).toLocaleDateString()} -
|
||||
{new Date(sub.current_period_end).toLocaleDateString()}
|
||||
</p>
|
||||
@@ -484,22 +484,22 @@ Proceed with activation?`;
|
||||
</div>
|
||||
|
||||
{/* Pricing */}
|
||||
<div className="grid grid-cols-3 gap-2 text-sm bg-white/50 p-3 rounded">
|
||||
<div className="grid grid-cols-3 gap-2 text-sm bg-background/50 p-3 rounded">
|
||||
<div>
|
||||
<p className="text-xs text-[#664fa3] mb-1">Base Fee</p>
|
||||
<p className="font-medium text-[#422268]">
|
||||
<p className="text-xs text-var(--purple-lavender) mb-1">Base Fee</p>
|
||||
<p className="font-medium text-var(--purple-ink)">
|
||||
${(sub.base_fee_cents / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-[#664fa3] mb-1">Donation</p>
|
||||
<p className="font-medium text-[#422268]">
|
||||
<p className="text-xs text-var(--purple-lavender) mb-1">Donation</p>
|
||||
<p className="font-medium text-var(--purple-ink)">
|
||||
${(sub.donation_cents / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-[#664fa3] mb-1">Total</p>
|
||||
<p className="font-semibold text-[#422268]">
|
||||
<p className="text-xs text-var(--purple-lavender) mb-1">Total</p>
|
||||
<p className="font-semibold text-var(--purple-ink)">
|
||||
${(sub.total_cents / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -512,7 +512,7 @@ Proceed with activation?`;
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleEdit(sub)}
|
||||
className="flex-1 text-[#664fa3] hover:bg-[#DDD8EB]"
|
||||
className="flex-1 text-var(--purple-lavender) hover:bg-var(--neutral-800)"
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
Edit
|
||||
@@ -534,7 +534,7 @@ Proceed with activation?`;
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<div className="p-12 text-center text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="p-12 text-center text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
No subscriptions found
|
||||
</div>
|
||||
)}
|
||||
@@ -544,29 +544,29 @@ Proceed with activation?`;
|
||||
<div className="hidden md:block overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="bg-[#DDD8EB]/20 border-b border-[#ddd8eb]">
|
||||
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<tr className="bg-var(--neutral-800)/20 border-b border-var(--neutral-800)">
|
||||
<th className="text-left p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Member
|
||||
</th>
|
||||
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-left p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Plan
|
||||
</th>
|
||||
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-left p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Status
|
||||
</th>
|
||||
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-left p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Period
|
||||
</th>
|
||||
<th className="text-right p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-right p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Base Fee
|
||||
</th>
|
||||
<th className="text-right p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-right p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Donation
|
||||
</th>
|
||||
<th className="text-right p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-right p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Total
|
||||
</th>
|
||||
<th className="text-center p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<th className="text-center p-4 text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
@@ -574,20 +574,20 @@ Proceed with activation?`;
|
||||
<tbody>
|
||||
{filteredSubscriptions.length > 0 ? (
|
||||
filteredSubscriptions.map((sub) => (
|
||||
<tr key={sub.id} className="border-b border-[#ddd8eb] hover:bg-[#f9f5ff] transition-colors">
|
||||
<tr key={sub.id} className="border-b border-var(--neutral-800) hover:bg-var(--lavender-400) transition-colors">
|
||||
<td className="p-4">
|
||||
<div className="font-medium text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<div className="font-medium text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{sub.user.first_name} {sub.user.last_name}
|
||||
</div>
|
||||
<div className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{sub.user.email}
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{sub.plan.name}
|
||||
</div>
|
||||
<div className="text-xs text-[#664fa3]">
|
||||
<div className="text-xs text-var(--purple-lavender)">
|
||||
{sub.plan.billing_cycle}
|
||||
</div>
|
||||
</td>
|
||||
@@ -597,18 +597,18 @@ Proceed with activation?`;
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="text-sm text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="text-sm text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div>{formatDate(sub.start_date)}</div>
|
||||
<div className="text-xs text-[#664fa3]">to {formatDate(sub.end_date)}</div>
|
||||
<div className="text-xs text-var(--purple-lavender)">to {formatDate(sub.end_date)}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4 text-right text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<td className="p-4 text-right text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(sub.base_subscription_cents || 0)}
|
||||
</td>
|
||||
<td className="p-4 text-right text-[#ff9e77]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<td className="p-4 text-right text-var(--orange-light)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(sub.donation_cents || 0)}
|
||||
</td>
|
||||
<td className="p-4 text-right font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<td className="p-4 text-right font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(sub.amount_paid_cents || 0)}
|
||||
</td>
|
||||
<td className="p-4">
|
||||
@@ -618,7 +618,7 @@ Proceed with activation?`;
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleEdit(sub)}
|
||||
className="text-[#664fa3] hover:bg-[#DDD8EB]"
|
||||
className="text-var(--purple-lavender) hover:bg-var(--neutral-800)"
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -639,7 +639,7 @@ Proceed with activation?`;
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="8" className="p-12 text-center text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<td colSpan="8" className="p-12 text-center text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
No subscriptions found
|
||||
</td>
|
||||
</tr>
|
||||
@@ -651,12 +651,12 @@ Proceed with activation?`;
|
||||
|
||||
{/* Edit Subscription Dialog */}
|
||||
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[500px] bg-white rounded-2xl">
|
||||
<DialogContent className="sm:max-w-[500px] bg-background rounded-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<DialogTitle className="text-2xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Edit Subscription
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<DialogDescription className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Update subscription status or end date for {selectedSubscription?.user.first_name} {selectedSubscription?.user.last_name}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -664,14 +664,14 @@ Proceed with activation?`;
|
||||
<div className="space-y-6 py-4">
|
||||
{/* Status */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="status" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Label htmlFor="status" className="text-var(--purple-ink) font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Status
|
||||
</Label>
|
||||
<Select
|
||||
value={editFormData.status}
|
||||
onValueChange={(value) => setEditFormData({ ...editFormData, status: value })}
|
||||
>
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="rounded-xl border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="Select status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -683,13 +683,12 @@ Proceed with activation?`;
|
||||
|
||||
{/* Warning Box - Show when status is different */}
|
||||
{selectedSubscription && editFormData.status !== selectedSubscription.status && (
|
||||
<div className={`mt-3 p-4 rounded-xl border-2 ${
|
||||
editFormData.status === 'cancelled'
|
||||
? 'bg-red-50 border-red-300'
|
||||
: editFormData.status === 'expired'
|
||||
<div className={`mt-3 p-4 rounded-xl border-2 ${editFormData.status === 'cancelled'
|
||||
? 'bg-red-50 border-red-300'
|
||||
: editFormData.status === 'expired'
|
||||
? 'bg-orange-50 border-orange-300'
|
||||
: 'bg-green-50 border-green-300'
|
||||
}`}>
|
||||
}`}>
|
||||
<div className="flex items-start gap-3">
|
||||
{editFormData.status === 'cancelled' || editFormData.status === 'expired' ? (
|
||||
<AlertTriangle className="h-5 w-5 text-red-600 flex-shrink-0 mt-0.5" />
|
||||
@@ -737,17 +736,17 @@ Proceed with activation?`;
|
||||
|
||||
{/* End Date */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="end_date" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Label htmlFor="end_date" className="text-var(--purple-ink) font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
End Date
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Calendar className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Calendar className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-var(--purple-lavender)" />
|
||||
<Input
|
||||
id="end_date"
|
||||
type="date"
|
||||
value={editFormData.end_date}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, end_date: e.target.value })}
|
||||
className="pl-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
className="pl-12 rounded-xl border-2 border-var(--neutral-800) focus:border-var(--purple-lavender)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -758,7 +757,7 @@ Proceed with activation?`;
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setEditDialogOpen(false)}
|
||||
className="rounded-full border-2 border-[#ddd8eb]"
|
||||
className="rounded-full border-2 border-var(--neutral-800)"
|
||||
disabled={isUpdating}
|
||||
>
|
||||
Cancel
|
||||
@@ -767,7 +766,7 @@ Proceed with activation?`;
|
||||
type="button"
|
||||
onClick={handleSaveSubscription}
|
||||
disabled={isUpdating}
|
||||
className="bg-[#81B29A] text-white hover:bg-[#6FA087] rounded-full"
|
||||
className="bg-var(--green-light) text-white hover:bg-var(--green-mint) rounded-full"
|
||||
>
|
||||
{isUpdating ? (
|
||||
<>
|
||||
|
||||
@@ -218,12 +218,12 @@ const AdminUserView = () => {
|
||||
</Button>
|
||||
|
||||
{/* User Profile Header */}
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] mb-8">
|
||||
<Card className="p-8 bg-background rounded-2xl border border-var(--neutral-800) mb-8">
|
||||
<div className="flex items-start gap-6">
|
||||
{/* Avatar */}
|
||||
<Avatar className="h-24 w-24 border-4 border-[#ddd8eb]">
|
||||
<Avatar className="h-24 w-24 border-4 border-var(--neutral-800)">
|
||||
<AvatarImage src={user.profile_photo_url} alt={`${user.first_name} ${user.last_name}`} />
|
||||
<AvatarFallback className="bg-[#DDD8EB] text-[#422268] font-semibold text-3xl">
|
||||
<AvatarFallback className="bg-var(--neutral-800) text-var(--purple-ink) font-semibold text-3xl">
|
||||
{user.first_name?.[0]}{user.last_name?.[0]}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -231,7 +231,7 @@ const AdminUserView = () => {
|
||||
{/* User Info */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h1 className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{user.first_name} {user.last_name}
|
||||
</h1>
|
||||
{/* Status & Role Badges */}
|
||||
@@ -240,7 +240,7 @@ const AdminUserView = () => {
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="grid md:grid-cols-2 gap-4 text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="grid md:grid-cols-2 gap-4 text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="h-4 w-4" />
|
||||
<span>{user.email}</span>
|
||||
@@ -263,8 +263,8 @@ const AdminUserView = () => {
|
||||
</Card>
|
||||
|
||||
{/* Admin Actions */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] mb-8">
|
||||
<h2 className="text-lg font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Card className="p-6 bg-background rounded-2xl border border-var(--neutral-800) mb-8">
|
||||
<h2 className="text-lg font-semibold text-var(--purple-ink) mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Admin Actions
|
||||
</h2>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
@@ -272,7 +272,7 @@ const AdminUserView = () => {
|
||||
onClick={handleResetPasswordRequest}
|
||||
disabled={resetPasswordLoading}
|
||||
variant="outline"
|
||||
className="border-2 border-[#664fa3] text-[#664fa3] hover:bg-[#f1eef9] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
className="border-2 border-var(--purple-lavender) text-var(--purple-lavender) hover:bg-var(--lavender-300) rounded-full px-4 py-2 disabled:opacity-50"
|
||||
>
|
||||
<Lock className="h-4 w-4 mr-2" />
|
||||
{resetPasswordLoading ? 'Resetting...' : 'Reset Password'}
|
||||
@@ -283,7 +283,7 @@ const AdminUserView = () => {
|
||||
onClick={handleResendVerificationRequest}
|
||||
disabled={resendVerificationLoading}
|
||||
variant="outline"
|
||||
className="border-2 border-[#ff9e77] text-[#ff9e77] hover:bg-[#FFF3E0] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
className="border-2 border-var(--orange-light) text-var(--orange-light) hover:bg-[#FFF3E0] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
>
|
||||
<Mail className="h-4 w-4 mr-2" />
|
||||
{resendVerificationLoading ? 'Sending...' : 'Resend Verification Email'}
|
||||
@@ -303,7 +303,7 @@ const AdminUserView = () => {
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploadingPhoto}
|
||||
variant="outline"
|
||||
className="border-2 border-[#81B29A] text-[#81B29A] hover:bg-[#E8F5E9] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
className="border-2 border-var(--green-light) text-var(--green-light) hover:bg-var(--green-bg) rounded-full px-4 py-2 disabled:opacity-50"
|
||||
>
|
||||
<Upload className="h-4 w-4 mr-2" />
|
||||
{uploadingPhoto ? 'Uploading...' : 'Upload Photo'}
|
||||
@@ -321,7 +321,7 @@ const AdminUserView = () => {
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-[#664fa3] ml-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="flex items-center gap-2 text-sm text-var(--purple-lavender) ml-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<span>User will receive a temporary password via email</span>
|
||||
</div>
|
||||
@@ -329,28 +329,28 @@ const AdminUserView = () => {
|
||||
</Card>
|
||||
|
||||
{/* Additional Details */}
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Card className="p-8 bg-background rounded-2xl border border-var(--neutral-800)">
|
||||
<h2 className="text-2xl font-semibold text-var(--purple-ink) mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Additional Information
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Address</label>
|
||||
<p className="text-[#422268] mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>{user.address}</p>
|
||||
<label className="text-sm font-medium text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Address</label>
|
||||
<p className="text-var(--purple-ink) mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>{user.address}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Date of Birth</label>
|
||||
<p className="text-[#422268] mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-sm font-medium text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Date of Birth</label>
|
||||
<p className="text-var(--purple-ink) mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{new Date(user.date_of_birth).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{user.partner_first_name && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Partner</label>
|
||||
<p className="text-[#422268] mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-sm font-medium text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Partner</label>
|
||||
<p className="text-var(--purple-ink) mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{user.partner_first_name} {user.partner_last_name}
|
||||
</p>
|
||||
</div>
|
||||
@@ -358,14 +358,14 @@ const AdminUserView = () => {
|
||||
|
||||
{user.referred_by_member_name && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Referred By</label>
|
||||
<p className="text-[#422268] mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>{user.referred_by_member_name}</p>
|
||||
<label className="text-sm font-medium text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Referred By</label>
|
||||
<p className="text-var(--purple-ink) mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>{user.referred_by_member_name}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{user.lead_sources && user.lead_sources.length > 0 && (
|
||||
<div className="md:col-span-2">
|
||||
<label className="text-sm font-medium text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Lead Sources</label>
|
||||
<label className="text-sm font-medium text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Lead Sources</label>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{user.lead_sources.map((source, idx) => (
|
||||
<Badge key={idx} variant="outline">{source}</Badge>
|
||||
@@ -378,32 +378,32 @@ const AdminUserView = () => {
|
||||
|
||||
{/* Subscription Info (if applicable) */}
|
||||
{user.role === 'member' && (
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] mt-8">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<Card className="p-8 bg-background rounded-2xl border border-var(--neutral-800) mt-8">
|
||||
<h2 className="text-2xl font-semibold text-var(--purple-ink) mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Subscription Information
|
||||
</h2>
|
||||
|
||||
{subscriptionsLoading ? (
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading subscriptions...</p>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading subscriptions...</p>
|
||||
) : subscriptions.length === 0 ? (
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>No subscriptions found for this member.</p>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>No subscriptions found for this member.</p>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{subscriptions.map((sub) => (
|
||||
<div key={sub.id} className="p-6 bg-[#F8F7FB] rounded-xl border border-[#ddd8eb]">
|
||||
<div key={sub.id} className="p-6 bg-var(--lavender-500) rounded-xl border border-var(--neutral-800)">
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<h3 className="text-lg font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{sub.plan.name}
|
||||
</h3>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{sub.plan.billing_cycle}
|
||||
</p>
|
||||
</div>
|
||||
<Badge className={
|
||||
sub.status === 'active' ? 'bg-[#81B29A] text-white' :
|
||||
sub.status === 'expired' ? 'bg-red-500 text-white' :
|
||||
'bg-gray-400 text-white'
|
||||
sub.status === 'active' ? 'bg-var(--green-light) text-white' :
|
||||
sub.status === 'expired' ? 'bg-red-500 text-white' :
|
||||
'bg-gray-400 text-white'
|
||||
}>
|
||||
{sub.status}
|
||||
</Badge>
|
||||
@@ -411,51 +411,51 @@ const AdminUserView = () => {
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Start Date</label>
|
||||
<p className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Start Date</label>
|
||||
<p className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{new Date(sub.start_date).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
{sub.end_date && (
|
||||
<div>
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>End Date</label>
|
||||
<p className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>End Date</label>
|
||||
<p className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{new Date(sub.end_date).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Base Amount</label>
|
||||
<p className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Base Amount</label>
|
||||
<p className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
${(sub.base_subscription_cents / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
{sub.donation_cents > 0 && (
|
||||
<div>
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Donation</label>
|
||||
<p className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Donation</label>
|
||||
<p className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
${(sub.donation_cents / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Paid</label>
|
||||
<p className="text-[#422268] font-semibold" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Paid</label>
|
||||
<p className="text-var(--purple-ink) font-semibold" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
${(sub.amount_paid_cents / 100).toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
{sub.payment_method && (
|
||||
<div>
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Payment Method</label>
|
||||
<p className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Payment Method</label>
|
||||
<p className="text-var(--purple-ink)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{sub.payment_method}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{sub.stripe_subscription_id && (
|
||||
<div className="md:col-span-2">
|
||||
<label className="text-[#664fa3] font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Stripe Subscription ID</label>
|
||||
<p className="text-[#422268] text-xs font-mono" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<label className="text-var(--purple-lavender) font-medium" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Stripe Subscription ID</label>
|
||||
<p className="text-var(--purple-ink) text-xs font-mono" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{sub.stripe_subscription_id}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -239,7 +239,7 @@ const AdminValidations = () => {
|
||||
const config = {
|
||||
pending_email: { label: 'Awaiting 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' },
|
||||
rejected: { label: 'Rejected', className: 'bg-red-100 text-red-700' }
|
||||
};
|
||||
@@ -279,44 +279,44 @@ const AdminValidations = () => {
|
||||
<>
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<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" }}>
|
||||
Validation Queue
|
||||
</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" }}>
|
||||
Review and validate pending membership applications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Card */}
|
||||
<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-2 md:grid-cols-5 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Pending</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Pending</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{pendingUsers.length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Awaiting Email</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Awaiting Email</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{pendingUsers.filter(u => u.status === 'pending_email').length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Validation</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Validation</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{pendingUsers.filter(u => u.status === 'pending_validation').length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pre-Validated</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender) mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pre-Validated</p>
|
||||
<p className="text-3xl font-semibold text-var(--purple-ink)" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{pendingUsers.filter(u => u.status === 'pre_validated').length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<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" }}>
|
||||
<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" }}>
|
||||
{pendingUsers.filter(u => u.status === 'payment_pending').length}
|
||||
</p>
|
||||
</div>
|
||||
@@ -330,20 +330,20 @@ const AdminValidations = () => {
|
||||
</Card>
|
||||
|
||||
{/* Filter Card */}
|
||||
<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 md:grid-cols-3 gap-4">
|
||||
<div className="relative md:col-span-2">
|
||||
<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, email, or phone..."
|
||||
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)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-var(--neutral-800)">
|
||||
<SelectValue placeholder="Filter by status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -361,16 +361,16 @@ const AdminValidations = () => {
|
||||
{/* Table */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading pending applications...</p>
|
||||
<p className="text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading pending applications...</p>
|
||||
</div>
|
||||
) : filteredUsers.length > 0 ? (
|
||||
<>
|
||||
<Card className="bg-white rounded-2xl border border-[#ddd8eb] overflow-hidden">
|
||||
<Card className="bg-background rounded-2xl border border-var(--neutral-800) overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-[#DDD8EB]/20"
|
||||
className="cursor-pointer hover:bg-var(--neutral-800)/20"
|
||||
onClick={() => handleSort('first_name')}
|
||||
>
|
||||
Name {renderSortIcon('first_name')}
|
||||
@@ -378,13 +378,13 @@ const AdminValidations = () => {
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Phone</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-[#DDD8EB]/20"
|
||||
className="cursor-pointer hover:bg-var(--neutral-800)/20"
|
||||
onClick={() => handleSort('status')}
|
||||
>
|
||||
Status {renderSortIcon('status')}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-[#DDD8EB]/20"
|
||||
className="cursor-pointer hover:bg-var(--neutral-800)/20"
|
||||
onClick={() => handleSort('created_at')}
|
||||
>
|
||||
Registered {renderSortIcon('created_at')}
|
||||
@@ -415,7 +415,7 @@ const AdminValidations = () => {
|
||||
onClick={() => handleReactivateUser(user)}
|
||||
disabled={actionLoading === user.id}
|
||||
size="sm"
|
||||
className="bg-[#81B29A] text-white hover:bg-[#6FA087]"
|
||||
className="bg-var(--green-light) text-white hover:bg-var(--green-mint)"
|
||||
>
|
||||
{actionLoading === user.id ? 'Reactivating...' : 'Reactivate'}
|
||||
</Button>
|
||||
@@ -426,7 +426,7 @@ const AdminValidations = () => {
|
||||
onClick={() => handleBypassAndValidateRequest(user)}
|
||||
disabled={actionLoading === user.id}
|
||||
size="sm"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white"
|
||||
className="bg-var(--neutral-800) text-var(--purple-ink) hover:bg-background"
|
||||
>
|
||||
{actionLoading === user.id ? 'Validating...' : 'Bypass & Validate'}
|
||||
</Button>
|
||||
@@ -450,7 +450,7 @@ const AdminValidations = () => {
|
||||
<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
|
||||
@@ -476,7 +476,7 @@ const AdminValidations = () => {
|
||||
onClick={() => handleValidateRequest(user)}
|
||||
disabled={actionLoading === user.id}
|
||||
size="sm"
|
||||
className="bg-[#81B29A] text-white hover:bg-[#6FA087]"
|
||||
className="bg-var(--green-light) text-white hover:bg-var(--green-mint)"
|
||||
>
|
||||
{actionLoading === user.id ? 'Validating...' : 'Validate'}
|
||||
</Button>
|
||||
@@ -507,7 +507,7 @@ const AdminValidations = () => {
|
||||
<div className="mt-8 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
{/* Page size selector */}
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Show</p>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Show</p>
|
||||
<Select
|
||||
value={itemsPerPage.toString()}
|
||||
onValueChange={(val) => {
|
||||
@@ -525,7 +525,7 @@ const AdminValidations = () => {
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p className="text-sm text-var(--purple-lavender)" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
entries (showing {(currentPage - 1) * itemsPerPage + 1}-
|
||||
{Math.min(currentPage * itemsPerPage, filteredUsers.length)} of {filteredUsers.length})
|
||||
</p>
|
||||
@@ -544,7 +544,7 @@ const AdminValidations = () => {
|
||||
|
||||
{[...Array(totalPages)].map((_, i) => {
|
||||
const showPage = i < 2 || i >= totalPages - 2 ||
|
||||
Math.abs(i - currentPage + 1) <= 1;
|
||||
Math.abs(i - currentPage + 1) <= 1;
|
||||
|
||||
if (!showPage && i === 2) {
|
||||
return (
|
||||
@@ -582,11 +582,11 @@ const AdminValidations = () => {
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Clock 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" }}>
|
||||
<Clock 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 Pending Validations
|
||||
</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'
|
||||
: 'All applications have been reviewed!'}
|
||||
|
||||
Reference in New Issue
Block a user