Update FE
This commit is contained in:
@@ -116,7 +116,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`
|
||||
bg-white border-r border-[#EAE0D5] transition-all duration-300 ease-out
|
||||
bg-white border-r border-[#ddd8eb] transition-all duration-300 ease-out
|
||||
${isMobile ? 'fixed inset-y-0 left-0 z-40' : 'relative'}
|
||||
${isOpen ? 'w-64' : 'w-16'}
|
||||
${isMobile && !isOpen ? '-translate-x-full' : 'translate-x-0'}
|
||||
@@ -124,23 +124,23 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-[#EAE0D5]">
|
||||
<div className="flex items-center justify-between p-4 border-b border-[#ddd8eb]">
|
||||
{isOpen && (
|
||||
<h2 className="text-xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Admin
|
||||
</h2>
|
||||
)}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="p-2 rounded-lg hover:bg-[#F2CC8F]/20 transition-colors ml-auto"
|
||||
className="p-2 rounded-lg hover:bg-[#DDD8EB]/20 transition-colors ml-auto"
|
||||
aria-label={isOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
||||
>
|
||||
{isMobile ? (
|
||||
<Menu className="h-5 w-5 text-[#3D405B]" />
|
||||
<Menu className="h-5 w-5 text-[#422268]" />
|
||||
) : isOpen ? (
|
||||
<ChevronLeft className="h-5 w-5 text-[#3D405B]" />
|
||||
<ChevronLeft className="h-5 w-5 text-[#422268]" />
|
||||
) : (
|
||||
<ChevronRight className="h-5 w-5 text-[#3D405B]" />
|
||||
<ChevronRight className="h-5 w-5 text-[#422268]" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
@@ -163,16 +163,16 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
className={`
|
||||
flex items-center gap-3 px-4 py-3 rounded-lg transition-all relative
|
||||
${item.disabled
|
||||
? 'opacity-50 cursor-not-allowed text-[#6B708D]'
|
||||
? 'opacity-50 cursor-not-allowed text-[#664fa3]'
|
||||
: active
|
||||
? 'bg-[#E07A5F]/10 text-[#E07A5F]'
|
||||
: 'text-[#3D405B] hover:bg-[#F2CC8F]/20'
|
||||
? 'bg-[#ff9e77]/10 text-[#ff9e77]'
|
||||
: 'text-[#422268] hover:bg-[#DDD8EB]/20'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{/* Active border */}
|
||||
{active && !item.disabled && (
|
||||
<div className="absolute left-0 top-0 bottom-0 w-1 bg-[#E07A5F] rounded-r" />
|
||||
<div className="absolute left-0 top-0 bottom-0 w-1 bg-[#ff9e77] rounded-r" />
|
||||
)}
|
||||
|
||||
<Icon className="h-5 w-5 flex-shrink-0" />
|
||||
@@ -181,12 +181,12 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
<>
|
||||
<span className="flex-1">{item.name}</span>
|
||||
{item.disabled && (
|
||||
<Badge className="bg-[#F2CC8F] text-[#3D405B] text-xs px-2 py-0.5">
|
||||
<Badge className="bg-[#DDD8EB] text-[#422268] text-xs px-2 py-0.5">
|
||||
Soon
|
||||
</Badge>
|
||||
)}
|
||||
{item.badge > 0 && !item.disabled && (
|
||||
<Badge className="bg-[#E07A5F] text-white text-xs px-2 py-0.5">
|
||||
<Badge className="bg-[#ff9e77] text-white text-xs px-2 py-0.5">
|
||||
{item.badge}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -195,7 +195,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
|
||||
{/* Badge when collapsed */}
|
||||
{!isOpen && item.badge > 0 && !item.disabled && (
|
||||
<div className="absolute -top-1 -right-1 bg-[#E07A5F] text-white text-xs rounded-full h-5 w-5 flex items-center justify-center font-medium">
|
||||
<div className="absolute -top-1 -right-1 bg-[#ff9e77] text-white text-xs rounded-full h-5 w-5 flex items-center justify-center font-medium">
|
||||
{item.badge}
|
||||
</div>
|
||||
)}
|
||||
@@ -203,7 +203,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
|
||||
{/* Tooltip when collapsed */}
|
||||
{!isOpen && (
|
||||
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-[#3D405B] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
||||
<div className="absolute left-full ml-2 top-1/2 -translate-y-1/2 px-3 py-2 bg-[#422268] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
||||
{item.name}
|
||||
{item.badge > 0 && ` (${item.badge})`}
|
||||
</div>
|
||||
@@ -214,18 +214,18 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
</nav>
|
||||
|
||||
{/* User Section */}
|
||||
<div className="border-t border-[#EAE0D5] p-4 space-y-2">
|
||||
<div className="border-t border-[#ddd8eb] p-4 space-y-2">
|
||||
{isOpen && user && (
|
||||
<div className="px-4 py-3 mb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-10 w-10 rounded-full bg-[#F2CC8F] flex items-center justify-center text-[#3D405B] font-semibold">
|
||||
<div className="h-10 w-10 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] font-semibold">
|
||||
{user.first_name?.[0]}{user.last_name?.[0]}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-[#3D405B] truncate">
|
||||
<p className="text-sm font-medium text-[#422268] truncate" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{user.first_name} {user.last_name}
|
||||
</p>
|
||||
<p className="text-xs text-[#6B708D] capitalize truncate">
|
||||
<p className="text-xs text-[#664fa3] capitalize truncate" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{user.role}
|
||||
</p>
|
||||
</div>
|
||||
@@ -238,7 +238,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
onClick={handleLogout}
|
||||
className={`
|
||||
flex items-center gap-3 px-4 py-3 rounded-lg w-full
|
||||
text-[#E07A5F] hover:bg-[#E07A5F]/10 transition-colors
|
||||
text-[#ff9e77] hover:bg-[#ff9e77]/10 transition-colors
|
||||
${!isOpen && 'justify-center'}
|
||||
`}
|
||||
>
|
||||
@@ -249,7 +249,7 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
|
||||
{/* Logout tooltip when collapsed */}
|
||||
{!isOpen && (
|
||||
<div className="relative group">
|
||||
<div className="absolute left-full ml-2 bottom-0 px-3 py-2 bg-[#3D405B] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
||||
<div className="absolute left-full ml-2 bottom-0 px-3 py-2 bg-[#422268] text-white text-sm rounded-lg opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity whitespace-nowrap z-50">
|
||||
Logout
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -57,19 +57,19 @@ export const AttendanceDialog = ({ event, open, onOpenChange, onSuccess }) => {
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto bg-white">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl fraunces text-[#3D405B]">
|
||||
<DialogTitle className="text-2xl text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Mark Attendance: {event?.title}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4 mt-4">
|
||||
{rsvps.length === 0 ? (
|
||||
<p className="text-center text-[#6B708D] py-8">No RSVPs yet</p>
|
||||
<p className="text-center text-[#664fa3] py-8" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>No RSVPs yet</p>
|
||||
) : (
|
||||
rsvps.map((rsvp) => (
|
||||
<div
|
||||
key={rsvp.user_id}
|
||||
className="flex items-center gap-3 p-4 border-2 border-[#EAE0D5] rounded-xl hover:border-[#E07A5F] transition-colors"
|
||||
className="flex items-center gap-3 p-4 border-2 border-[#ddd8eb] rounded-xl hover:border-[#664fa3] transition-colors"
|
||||
>
|
||||
<Checkbox
|
||||
checked={attendance[rsvp.user_id] || false}
|
||||
@@ -79,8 +79,8 @@ export const AttendanceDialog = ({ event, open, onOpenChange, onSuccess }) => {
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-[#3D405B]">{rsvp.user_name}</p>
|
||||
<p className="text-sm text-[#6B708D]">{rsvp.user_email}</p>
|
||||
<p className="font-medium text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>{rsvp.user_name}</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>{rsvp.user_email}</p>
|
||||
</div>
|
||||
{rsvp.attended && (
|
||||
<span className="text-sm text-[#81B29A] font-medium">
|
||||
@@ -96,14 +96,14 @@ export const AttendanceDialog = ({ event, open, onOpenChange, onSuccess }) => {
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
className="flex-1 bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full"
|
||||
className="flex-1 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
>
|
||||
{loading ? 'Saving...' : 'Save Attendance'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => onOpenChange(false)}
|
||||
variant="outline"
|
||||
className="flex-1 border-2 border-[#6B708D] text-[#6B708D] hover:bg-[#6B708D] hover:text-white rounded-full"
|
||||
className="flex-1 border-2 border-[#ddd8eb] text-[#664fa3] hover:bg-white hover:text-[#422268] rounded-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -69,14 +69,14 @@ const ChangePasswordDialog = ({ open, onOpenChange }) => {
|
||||
<DialogContent className="sm:max-w-md bg-white">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-[#FFF3E0]">
|
||||
<Lock className="h-5 w-5 text-[#E07A5F]" />
|
||||
<div className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-[#f1eef9]">
|
||||
<Lock className="h-5 w-5 text-[#ff9e77]" />
|
||||
</div>
|
||||
<DialogTitle className="text-2xl font-semibold text-[#3D405B]">
|
||||
<DialogTitle className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Change Password
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<DialogDescription className="text-[#6B708D]">
|
||||
<DialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Update your password to keep your account secure.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -92,7 +92,7 @@ const ChangePasswordDialog = ({ open, onOpenChange }) => {
|
||||
value={formData.currentPassword}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter current password"
|
||||
className="h-12 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -106,7 +106,7 @@ const ChangePasswordDialog = ({ open, onOpenChange }) => {
|
||||
value={formData.newPassword}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter new password (min. 6 characters)"
|
||||
className="h-12 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@ const ChangePasswordDialog = ({ open, onOpenChange }) => {
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Re-enter new password"
|
||||
className="h-12 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -136,7 +136,7 @@ const ChangePasswordDialog = ({ open, onOpenChange }) => {
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-6 disabled:opacity-50"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6 disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Changing...' : 'Change Password'}
|
||||
</Button>
|
||||
|
||||
@@ -14,12 +14,12 @@ const Navbar = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="bg-white border-b border-[#EAE0D5] sticky top-0 z-50 backdrop-blur-sm bg-white/90">
|
||||
<nav className="bg-white border-b border-[#ddd8eb] sticky top-0 z-50 backdrop-blur-sm bg-white/90">
|
||||
<div className="max-w-7xl mx-auto px-6 py-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<Link to={user ? "/dashboard" : "/"} className="flex items-center gap-2">
|
||||
<Users className="h-8 w-8 text-[#E07A5F]" strokeWidth={1.5} />
|
||||
<span className="text-2xl font-semibold fraunces text-[#3D405B]">Membership</span>
|
||||
<Users className="h-8 w-8 text-[#ff9e77]" strokeWidth={1.5} />
|
||||
<span className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>Membership</span>
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -29,7 +29,7 @@ const Navbar = () => {
|
||||
<Link to="/admin">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-[#3D405B] hover:text-[#E07A5F] hover:bg-[#F2CC8F]/10"
|
||||
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
|
||||
data-testid="admin-nav-button"
|
||||
>
|
||||
<LayoutDashboard className="h-4 w-4 mr-2" />
|
||||
@@ -40,7 +40,7 @@ const Navbar = () => {
|
||||
<Link to="/events">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-[#3D405B] hover:text-[#E07A5F] hover:bg-[#F2CC8F]/10"
|
||||
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
|
||||
data-testid="events-nav-button"
|
||||
>
|
||||
Events
|
||||
@@ -49,7 +49,7 @@ const Navbar = () => {
|
||||
<Link to="/profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-[#3D405B] hover:text-[#E07A5F] hover:bg-[#F2CC8F]/10"
|
||||
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
|
||||
data-testid="profile-nav-button"
|
||||
>
|
||||
Profile
|
||||
@@ -57,7 +57,7 @@ const Navbar = () => {
|
||||
</Link>
|
||||
<Button
|
||||
onClick={handleLogout}
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-6"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||
data-testid="logout-button"
|
||||
>
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
@@ -69,7 +69,7 @@ const Navbar = () => {
|
||||
<Link to="/login">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-[#3D405B] hover:text-[#E07A5F] hover:bg-[#F2CC8F]/10"
|
||||
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
|
||||
data-testid="login-nav-button"
|
||||
>
|
||||
Login
|
||||
@@ -77,7 +77,7 @@ const Navbar = () => {
|
||||
</Link>
|
||||
<Link to="/register">
|
||||
<Button
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-6"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||
data-testid="register-nav-button"
|
||||
>
|
||||
Join Us
|
||||
|
||||
@@ -126,10 +126,10 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px] bg-white rounded-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<DialogTitle className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Activate Manual Payment
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-[#6B708D]">
|
||||
<DialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Record offline payment for {user.first_name} {user.last_name} ({user.email})
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -137,7 +137,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
<form onSubmit={handleSubmit} className="space-y-6 py-4">
|
||||
{/* Subscription Plan Selection */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="plan_id" className="text-[#3D405B] font-medium">
|
||||
<Label htmlFor="plan_id" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Subscription Plan
|
||||
</Label>
|
||||
<Select
|
||||
@@ -152,7 +152,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#EAE0D5]">
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectValue placeholder="Select subscription plan" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -164,7 +164,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{selectedPlan && (
|
||||
<p className="text-xs text-[#6B708D]">
|
||||
<p className="text-xs text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{selectedPlan.description || `${selectedPlan.billing_cycle} subscription`}
|
||||
</p>
|
||||
)}
|
||||
@@ -172,7 +172,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
|
||||
{/* Payment Amount */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="amount" className="text-[#3D405B] font-medium">
|
||||
<Label htmlFor="amount" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Payment Amount ($)
|
||||
</Label>
|
||||
<Input
|
||||
@@ -183,27 +183,27 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
placeholder="Enter amount"
|
||||
value={formData.amount}
|
||||
onChange={(e) => setFormData({...formData, amount: e.target.value})}
|
||||
className="rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
required
|
||||
/>
|
||||
<p className="text-xs text-[#6B708D]">
|
||||
<p className="text-xs text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Amount can differ from plan price if offering a discount or partial payment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Payment Date */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment_date" className="text-[#3D405B] font-medium">
|
||||
<Label htmlFor="payment_date" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Payment Date
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Calendar className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#6B708D]" />
|
||||
<Calendar className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Input
|
||||
id="payment_date"
|
||||
type="date"
|
||||
value={formData.payment_date}
|
||||
onChange={(e) => setFormData({...formData, payment_date: e.target.value})}
|
||||
className="pl-12 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="pl-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -211,14 +211,14 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
|
||||
{/* Payment Method */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="payment_method" className="text-[#3D405B] font-medium">
|
||||
<Label htmlFor="payment_method" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Payment Method
|
||||
</Label>
|
||||
<Select
|
||||
value={formData.payment_method}
|
||||
onValueChange={(value) => setFormData({...formData, payment_method: value})}
|
||||
>
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#EAE0D5]">
|
||||
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectValue placeholder="Select payment method" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -232,7 +232,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
|
||||
{/* Subscription Period */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-[#3D405B] font-medium">Subscription Period</Label>
|
||||
<Label className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>Subscription Period</Label>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
@@ -240,9 +240,9 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
id="use_custom_period"
|
||||
checked={useCustomPeriod}
|
||||
onChange={(e) => setUseCustomPeriod(e.target.checked)}
|
||||
className="rounded border-[#EAE0D5]"
|
||||
className="rounded border-[#ddd8eb]"
|
||||
/>
|
||||
<Label htmlFor="use_custom_period" className="text-sm text-[#6B708D] font-normal cursor-pointer">
|
||||
<Label htmlFor="use_custom_period" className="text-sm text-[#664fa3] font-normal cursor-pointer" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Use custom dates instead of plan's billing cycle
|
||||
</Label>
|
||||
</div>
|
||||
@@ -250,7 +250,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
{useCustomPeriod ? (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="custom_period_start" className="text-sm text-[#3D405B]">
|
||||
<Label htmlFor="custom_period_start" className="text-sm text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Start Date
|
||||
</Label>
|
||||
<Input
|
||||
@@ -258,12 +258,12 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
type="date"
|
||||
value={formData.custom_period_start}
|
||||
onChange={(e) => setFormData({...formData, custom_period_start: e.target.value})}
|
||||
className="rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
required={useCustomPeriod}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="custom_period_end" className="text-sm text-[#3D405B]">
|
||||
<Label htmlFor="custom_period_end" className="text-sm text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
End Date
|
||||
</Label>
|
||||
<Input
|
||||
@@ -271,14 +271,14 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
type="date"
|
||||
value={formData.custom_period_end}
|
||||
onChange={(e) => setFormData({...formData, custom_period_end: e.target.value})}
|
||||
className="rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
required={useCustomPeriod}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
selectedPlan && (
|
||||
<p className="text-sm text-[#6B708D] bg-[#F2CC8F]/10 p-3 rounded-lg">
|
||||
<p className="text-sm text-[#664fa3] bg-[#f1eef9] p-3 rounded-lg" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Will use plan's billing cycle: <span className="font-medium">{selectedPlan.billing_cycle}</span>
|
||||
<br />
|
||||
Starts today, ends {selectedPlan.billing_cycle === 'monthly' ? '30 days' :
|
||||
@@ -292,7 +292,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
|
||||
{/* Notes */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="notes" className="text-[#3D405B] font-medium">
|
||||
<Label htmlFor="notes" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Notes (Optional)
|
||||
</Label>
|
||||
<Textarea
|
||||
@@ -300,7 +300,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
placeholder="Additional notes about the payment..."
|
||||
value={formData.notes}
|
||||
onChange={(e) => setFormData({...formData, notes: e.target.value})}
|
||||
className="rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F] min-h-[100px]"
|
||||
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3] min-h-[100px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -309,7 +309,7 @@ const PaymentActivationDialog = ({ open, onOpenChange, user, onSuccess }) => {
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="rounded-full border-2 border-[#EAE0D5]"
|
||||
className="rounded-full border-2 border-[#ddd8eb]"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -94,10 +94,10 @@ const PlanDialog = ({ open, onOpenChange, plan, onSuccess }) => {
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="fraunces text-2xl">
|
||||
<DialogTitle className="text-2xl text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{plan ? 'Edit Plan' : 'Create New Plan'}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<DialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{plan ? 'Update plan details below' : 'Enter plan details to create a new subscription plan'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -175,7 +175,7 @@ const PlanDialog = ({ open, onOpenChange, plan, onSuccess }) => {
|
||||
placeholder="price_xxxxxxxxxxxxx"
|
||||
className="mt-2 font-mono text-sm"
|
||||
/>
|
||||
<p className="text-sm text-[#6B708D] mt-1">
|
||||
<p className="text-sm text-[#664fa3] mt-1" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Optional. Leave empty for manual/test plans.
|
||||
</p>
|
||||
</div>
|
||||
@@ -184,7 +184,7 @@ const PlanDialog = ({ open, onOpenChange, plan, onSuccess }) => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label htmlFor="active">Active Status</Label>
|
||||
<p className="text-sm text-[#6B708D]">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Inactive plans won't appear for new subscriptions
|
||||
</p>
|
||||
</div>
|
||||
@@ -196,7 +196,7 @@ const PlanDialog = ({ open, onOpenChange, plan, onSuccess }) => {
|
||||
onChange={(e) => setFormData({ ...formData, active: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#E07A5F]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#81B29A]"></div>
|
||||
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-[#664fa3]/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-[#81B29A]"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -212,7 +212,7 @@ const PlanDialog = ({ open, onOpenChange, plan, onSuccess }) => {
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="bg-[#E07A5F] hover:bg-[#D0694E]"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
|
||||
@@ -26,7 +26,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
<div className="space-y-8">
|
||||
{/* Personal Information */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Personal Information
|
||||
</h2>
|
||||
|
||||
@@ -40,7 +40,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.first_name}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="first-name-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -52,7 +52,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.last_name}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="last-name-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.phone}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="phone-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -82,7 +82,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.date_of_birth}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="dob-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.city}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="city-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -124,7 +124,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.state}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="state-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
required
|
||||
value={formData.zipcode}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="zipcode-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -145,7 +145,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
|
||||
{/* How Did You Hear About Us */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
How Did You Hear About Us? *
|
||||
</h2>
|
||||
<div className="space-y-3">
|
||||
@@ -167,7 +167,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
|
||||
{/* Partner Information */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Partner Information (Optional)
|
||||
</h2>
|
||||
|
||||
@@ -179,7 +179,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
name="partner_first_name"
|
||||
value={formData.partner_first_name}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="partner-first-name-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -190,7 +190,7 @@ const RegistrationStep1 = ({ formData, setFormData, handleInputChange }) => {
|
||||
name="partner_last_name"
|
||||
value={formData.partner_last_name}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="partner-last-name-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -33,10 +33,10 @@ const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
|
||||
<div className="space-y-8">
|
||||
{/* Newsletter Publication Preferences */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Newsletter Publication Preferences *
|
||||
</h2>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Please check what information may be published in LOAF Newsletter
|
||||
</p>
|
||||
|
||||
@@ -97,7 +97,7 @@ const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
|
||||
|
||||
{/* Referral */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Referral
|
||||
</h2>
|
||||
<div>
|
||||
@@ -110,10 +110,10 @@ const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
|
||||
value={formData.referred_by_member_name}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter member name or email"
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="referral-input"
|
||||
/>
|
||||
<p className="text-sm text-[#6B708D] mt-2">
|
||||
<p className="text-sm text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
If referred by a current member, you may skip the event attendance requirement.
|
||||
</p>
|
||||
</div>
|
||||
@@ -121,10 +121,10 @@ const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
|
||||
|
||||
{/* Volunteer Interests */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Volunteer Interests (Optional)
|
||||
</h2>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
I may at some time be interested in volunteering with LOAF in the following ways (training is provided)
|
||||
</p>
|
||||
|
||||
@@ -158,7 +158,7 @@ const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
|
||||
I am requesting for scholarship
|
||||
</Label>
|
||||
</div>
|
||||
<p className="text-sm text-[#6B708D]">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Scholarship information is kept confidential
|
||||
</p>
|
||||
|
||||
@@ -174,7 +174,7 @@ const RegistrationStep2 = ({ formData, setFormData, handleInputChange }) => {
|
||||
onChange={handleInputChange}
|
||||
placeholder="Tell us why you're requesting a scholarship..."
|
||||
rows={4}
|
||||
className="rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -23,11 +23,11 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Members Directory
|
||||
</h2>
|
||||
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Would you like to be displayed on our private members directory? (optional and you can change the answer later)
|
||||
</p>
|
||||
|
||||
@@ -37,8 +37,8 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
className={`
|
||||
p-4 rounded-xl border-2 cursor-pointer transition-all
|
||||
${formData.show_in_directory
|
||||
? 'border-[#E07A5F] bg-[#E07A5F]/5'
|
||||
: 'border-[#EAE0D5] hover:border-[#6B708D]'
|
||||
? 'border-[#ff9e77] bg-[#ff9e77]/5'
|
||||
: 'border-[#ddd8eb] hover:border-[#664fa3]'
|
||||
}
|
||||
`}
|
||||
onClick={() => setFormData(prev => ({ ...prev, show_in_directory: true }))}
|
||||
@@ -46,13 +46,13 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`
|
||||
w-5 h-5 rounded-full border-2 flex items-center justify-center
|
||||
${formData.show_in_directory ? 'border-[#E07A5F]' : 'border-[#EAE0D5]'}
|
||||
${formData.show_in_directory ? 'border-[#ff9e77]' : 'border-[#ddd8eb]'}
|
||||
`}>
|
||||
{formData.show_in_directory && (
|
||||
<div className="w-3 h-3 rounded-full bg-[#E07A5F]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#ff9e77]" />
|
||||
)}
|
||||
</div>
|
||||
<span className="font-medium text-[#3D405B]">
|
||||
<span className="font-medium text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Yes, include me in the Members Directory
|
||||
</span>
|
||||
</div>
|
||||
@@ -62,8 +62,8 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
className={`
|
||||
p-4 rounded-xl border-2 cursor-pointer transition-all
|
||||
${!formData.show_in_directory
|
||||
? 'border-[#E07A5F] bg-[#E07A5F]/5'
|
||||
: 'border-[#EAE0D5] hover:border-[#6B708D]'
|
||||
? 'border-[#ff9e77] bg-[#ff9e77]/5'
|
||||
: 'border-[#ddd8eb] hover:border-[#664fa3]'
|
||||
}
|
||||
`}
|
||||
onClick={() => setFormData(prev => ({ ...prev, show_in_directory: false }))}
|
||||
@@ -71,13 +71,13 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`
|
||||
w-5 h-5 rounded-full border-2 flex items-center justify-center
|
||||
${!formData.show_in_directory ? 'border-[#E07A5F]' : 'border-[#EAE0D5]'}
|
||||
${!formData.show_in_directory ? 'border-[#ff9e77]' : 'border-[#ddd8eb]'}
|
||||
`}>
|
||||
{!formData.show_in_directory && (
|
||||
<div className="w-3 h-3 rounded-full bg-[#E07A5F]" />
|
||||
<div className="w-3 h-3 rounded-full bg-[#ff9e77]" />
|
||||
)}
|
||||
</div>
|
||||
<span className="font-medium text-[#3D405B]">
|
||||
<span className="font-medium text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
No, don't include me in the Members Directory
|
||||
</span>
|
||||
</div>
|
||||
@@ -87,8 +87,8 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
|
||||
{/* Conditional Directory Fields */}
|
||||
{formData.show_in_directory && (
|
||||
<div className="space-y-4 mt-6 p-6 bg-[#FDFCF8] rounded-xl border border-[#EAE0D5]">
|
||||
<p className="text-[#6B708D] text-sm">
|
||||
<div className="space-y-4 mt-6 p-6 bg-white rounded-xl border border-[#ddd8eb]">
|
||||
<p className="text-[#664fa3] text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Below, choose what information you would like include in the Members Only Directory.
|
||||
(If you ever want to update this information, remember the Directory Section and Account Section are separate)
|
||||
</p>
|
||||
@@ -101,7 +101,7 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
type="email"
|
||||
value={formData.directory_email}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -114,7 +114,7 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
onChange={handleInputChange}
|
||||
placeholder="Tell other members about yourself..."
|
||||
rows={4}
|
||||
className="rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -125,7 +125,7 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
name="directory_address"
|
||||
value={formData.directory_address}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -137,7 +137,7 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
type="tel"
|
||||
value={formData.directory_phone}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +149,7 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
type="date"
|
||||
value={formData.directory_dob}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -162,7 +162,7 @@ const RegistrationStep3 = ({ formData, setFormData, handleInputChange }) => {
|
||||
name="directory_partner_name"
|
||||
value={formData.directory_partner_name}
|
||||
onChange={handleInputChange}
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,11 @@ const RegistrationStep4 = ({ formData, handleInputChange }) => {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Account Credentials
|
||||
</h2>
|
||||
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Your email is also your username that you can use to login.
|
||||
Please note you can only login after your application is approved.
|
||||
</p>
|
||||
@@ -28,7 +28,7 @@ const RegistrationStep4 = ({ formData, handleInputChange }) => {
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
placeholder="your.email@example.com"
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="email-input"
|
||||
/>
|
||||
</div>
|
||||
@@ -43,10 +43,10 @@ const RegistrationStep4 = ({ formData, handleInputChange }) => {
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
placeholder="At least 6 characters"
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="password-input"
|
||||
/>
|
||||
<p className="text-sm text-[#6B708D] mt-2">
|
||||
<p className="text-sm text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Must be at least 6 characters long
|
||||
</p>
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@ const RegistrationStep4 = ({ formData, handleInputChange }) => {
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Re-enter your password"
|
||||
className="h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="confirm-password-input"
|
||||
/>
|
||||
{formData.confirmPassword && formData.password !== formData.confirmPassword && (
|
||||
|
||||
@@ -20,25 +20,25 @@ const RegistrationStepIndicator = ({ currentStep, totalSteps = 4 }) => {
|
||||
w-12 h-12 rounded-full flex items-center justify-center font-semibold text-lg
|
||||
transition-all duration-300
|
||||
${currentStep === step.number
|
||||
? 'bg-[#E07A5F] text-white scale-110 shadow-lg'
|
||||
? 'bg-[#ff9e77] text-white scale-110 shadow-lg'
|
||||
: currentStep > step.number
|
||||
? 'bg-[#81B29A] text-white'
|
||||
: 'bg-[#EAE0D5] text-[#6B708D]'
|
||||
: 'bg-[#ddd8eb] text-[#664fa3]'
|
||||
}
|
||||
`}>
|
||||
{currentStep > step.number ? '✓' : step.number}
|
||||
</div>
|
||||
<span className={`
|
||||
text-sm mt-2 font-medium transition-colors
|
||||
${currentStep === step.number ? 'text-[#E07A5F]' : 'text-[#6B708D]'}
|
||||
`}>
|
||||
${currentStep === step.number ? 'text-[#ff9e77]' : 'text-[#664fa3]'}
|
||||
`} style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{step.title}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Connecting Line */}
|
||||
{index < steps.length - 1 && (
|
||||
<div className="flex-1 h-1 mx-2 relative -top-6 bg-[#EAE0D5]">
|
||||
<div className="flex-1 h-1 mx-2 relative -top-6 bg-[#ddd8eb]">
|
||||
<div
|
||||
className={`
|
||||
h-full transition-all duration-500
|
||||
@@ -52,8 +52,8 @@ const RegistrationStepIndicator = ({ currentStep, totalSteps = 4 }) => {
|
||||
</div>
|
||||
|
||||
{/* Step Counter */}
|
||||
<p className="text-center text-[#6B708D] mt-6 text-lg">
|
||||
Step <span className="font-semibold text-[#E07A5F]">{currentStep}</span> of {totalSteps}
|
||||
<p className="text-center text-[#664fa3] mt-6 text-lg" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Step <span className="font-semibold text-[#ff9e77]">{currentStep}</span> of {totalSteps}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ const AdminLayout = ({ children }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-[#FDFCF8]">
|
||||
<div className="flex h-screen bg-white">
|
||||
{/* Sidebar */}
|
||||
<AdminSidebar
|
||||
isOpen={sidebarOpen}
|
||||
|
||||
@@ -46,10 +46,10 @@ const EventDetails = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#FDFCF8]">
|
||||
<div className="min-h-screen bg-white">
|
||||
<Navbar />
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<p className="text-[#6B708D]">Loading event...</p>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading event...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -60,24 +60,24 @@ const EventDetails = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#FDFCF8]">
|
||||
<div className="min-h-screen bg-white">
|
||||
<Navbar />
|
||||
|
||||
|
||||
<div className="max-w-4xl mx-auto px-6 py-12">
|
||||
<button
|
||||
onClick={() => navigate('/events')}
|
||||
className="inline-flex items-center text-[#6B708D] hover:text-[#E07A5F] transition-colors mb-8"
|
||||
className="inline-flex items-center text-[#664fa3] hover:text-[#ff9e77] transition-colors mb-8"
|
||||
data-testid="back-to-events-button"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||
Back to Events
|
||||
</button>
|
||||
|
||||
<Card className="p-8 md:p-12 bg-white rounded-2xl border border-[#EAE0D5] shadow-lg">
|
||||
<Card className="p-8 md:p-12 bg-white rounded-2xl border border-[#ddd8eb] shadow-lg">
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="bg-[#F2CC8F]/20 p-4 rounded-xl">
|
||||
<Calendar className="h-10 w-10 text-[#E07A5F]" />
|
||||
<div className="bg-[#DDD8EB]/20 p-4 rounded-xl">
|
||||
<Calendar className="h-10 w-10 text-[#664fa3]" />
|
||||
</div>
|
||||
{event.user_rsvp_status && (
|
||||
<Badge
|
||||
@@ -85,8 +85,8 @@ const EventDetails = () => {
|
||||
event.user_rsvp_status === 'yes'
|
||||
? 'bg-[#81B29A] text-white'
|
||||
: event.user_rsvp_status === 'no'
|
||||
? 'bg-[#6B708D] text-white'
|
||||
: 'bg-[#F2CC8F] text-[#3D405B]'
|
||||
? 'bg-gray-400 text-white'
|
||||
: 'bg-orange-100 text-orange-700'
|
||||
}`}
|
||||
>
|
||||
{event.user_rsvp_status === 'yes' && 'Going'}
|
||||
@@ -96,14 +96,14 @@ const EventDetails = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-6">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{event.title}
|
||||
</h1>
|
||||
|
||||
<div className="space-y-4 text-lg">
|
||||
<div className="flex items-center gap-3 text-[#6B708D]">
|
||||
<div className="flex items-center gap-3 text-[#664fa3]">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<span>
|
||||
<span style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{new Date(event.start_at).toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
@@ -112,20 +112,20 @@ const EventDetails = () => {
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[#6B708D]">
|
||||
<div className="flex items-center gap-3 text-[#664fa3]">
|
||||
<Calendar className="h-5 w-5" />
|
||||
<span>
|
||||
<span style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{new Date(event.start_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} -{' '}
|
||||
{new Date(event.end_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[#6B708D]">
|
||||
<div className="flex items-center gap-3 text-[#664fa3]">
|
||||
<MapPin className="h-5 w-5" />
|
||||
<span>{event.location}</span>
|
||||
<span style={{ fontFamily: "'Nunito Sans', sans-serif" }}>{event.location}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-[#6B708D]">
|
||||
<div className="flex items-center gap-3 text-[#664fa3]">
|
||||
<Users className="h-5 w-5" />
|
||||
<span>
|
||||
<span style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{event.rsvp_count || 0} {event.rsvp_count === 1 ? 'person' : 'people'} attending
|
||||
{event.capacity && ` (Capacity: ${event.capacity})`}
|
||||
</span>
|
||||
@@ -134,18 +134,18 @@ const EventDetails = () => {
|
||||
</div>
|
||||
|
||||
{event.description && (
|
||||
<div className="mb-8 pb-8 border-b border-[#EAE0D5]">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<div className="mb-8 pb-8 border-b border-[#ddd8eb]">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
About This Event
|
||||
</h2>
|
||||
<p className="text-[#6B708D] leading-relaxed whitespace-pre-line">
|
||||
<p className="text-[#664fa3] leading-relaxed whitespace-pre-line" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{event.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-6" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
RSVP to This Event
|
||||
</h2>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
@@ -155,7 +155,7 @@ const EventDetails = () => {
|
||||
className={`rounded-full px-8 py-6 flex items-center gap-2 ${
|
||||
event.user_rsvp_status === 'yes'
|
||||
? 'bg-[#81B29A] text-white'
|
||||
: 'bg-[#E07A5F] text-white hover:bg-[#D0694E]'
|
||||
: 'bg-[#DDD8EB] text-[#422268] hover:bg-white'
|
||||
}`}
|
||||
data-testid="rsvp-yes-button"
|
||||
>
|
||||
@@ -168,8 +168,8 @@ const EventDetails = () => {
|
||||
variant="outline"
|
||||
className={`rounded-full px-8 py-6 flex items-center gap-2 border-2 ${
|
||||
event.user_rsvp_status === 'maybe'
|
||||
? 'border-[#F2CC8F] bg-[#F2CC8F]/20 text-[#3D405B]'
|
||||
: 'border-[#3D405B] text-[#3D405B] hover:bg-[#F2CC8F]/10'
|
||||
? 'border-orange-400 bg-orange-100 text-orange-700'
|
||||
: 'border-[#664fa3] text-[#664fa3] hover:bg-[#f1eef9]'
|
||||
}`}
|
||||
data-testid="rsvp-maybe-button"
|
||||
>
|
||||
@@ -182,8 +182,8 @@ const EventDetails = () => {
|
||||
variant="outline"
|
||||
className={`rounded-full px-8 py-6 flex items-center gap-2 border-2 ${
|
||||
event.user_rsvp_status === 'no'
|
||||
? 'border-[#6B708D] bg-[#6B708D]/20 text-[#3D405B]'
|
||||
: 'border-[#6B708D] text-[#6B708D] hover:bg-[#6B708D]/10'
|
||||
? 'border-gray-400 bg-gray-100 text-gray-700'
|
||||
: 'border-gray-400 text-gray-600 hover:bg-gray-50'
|
||||
}`}
|
||||
data-testid="rsvp-no-button"
|
||||
>
|
||||
|
||||
@@ -9,61 +9,61 @@ const PaymentCancel = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#FDFCF8]">
|
||||
<div className="min-h-screen bg-white">
|
||||
<Navbar />
|
||||
|
||||
<div className="max-w-4xl mx-auto px-6 py-12">
|
||||
<div className="text-center mb-12">
|
||||
{/* Cancel Icon */}
|
||||
<div className="mb-8">
|
||||
<div className="bg-[#6B708D] rounded-full w-24 h-24 mx-auto flex items-center justify-center">
|
||||
<div className="bg-gray-400 rounded-full w-24 h-24 mx-auto flex items-center justify-center">
|
||||
<XCircle className="h-12 w-12 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cancel Message */}
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Payment Cancelled
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D] max-w-2xl mx-auto mb-8">
|
||||
<p className="text-lg text-[#664fa3] max-w-2xl mx-auto mb-8" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Your payment was cancelled. No charges have been made to your account.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Info Card */}
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] shadow-lg mb-8">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6 text-center">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] shadow-lg mb-8">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-6 text-center" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
What Happened?
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<p className="text-[#6B708D] text-center">
|
||||
<p className="text-[#664fa3] text-center" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
You cancelled the payment process or closed the checkout page. Your membership has not been activated yet.
|
||||
</p>
|
||||
|
||||
<div className="bg-[#F2CC8F]/20 p-6 rounded-xl">
|
||||
<h3 className="text-lg font-semibold text-[#3D405B] mb-4">
|
||||
<div className="bg-[#DDD8EB]/20 p-6 rounded-xl">
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Ready to Complete Your Membership?
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start gap-3">
|
||||
<CreditCard className="h-5 w-5 text-[#E07A5F] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#6B708D]">
|
||||
<CreditCard className="h-5 w-5 text-[#664fa3] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Return to the plans page to complete your subscription
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Mail className="h-5 w-5 text-[#E07A5F] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#6B708D]">
|
||||
<Mail className="h-5 w-5 text-[#664fa3] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Contact us if you experienced any issues during checkout
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#FDFCF8] p-6 rounded-xl">
|
||||
<p className="text-sm text-[#6B708D] text-center mb-4">
|
||||
<span className="font-medium text-[#3D405B]">Note:</span>{' '}
|
||||
<div className="bg-[#f1eef9] p-6 rounded-xl">
|
||||
<p className="text-sm text-[#664fa3] text-center mb-4" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<span className="font-medium text-[#422268]">Note:</span>{' '}
|
||||
Your membership application is still approved. You can complete payment whenever you're ready.
|
||||
</p>
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@ const PaymentCancel = () => {
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button
|
||||
onClick={() => navigate('/plans')}
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-8 py-6 text-lg font-semibold"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8 py-6 text-lg font-semibold"
|
||||
data-testid="try-again-button"
|
||||
>
|
||||
<CreditCard className="mr-2 h-5 w-5" />
|
||||
@@ -82,7 +82,7 @@ const PaymentCancel = () => {
|
||||
<Button
|
||||
onClick={() => navigate('/dashboard')}
|
||||
variant="outline"
|
||||
className="border-2 border-[#6B708D] text-[#6B708D] hover:bg-[#6B708D] hover:text-white rounded-full px-8 py-6 text-lg font-semibold"
|
||||
className="border-2 border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white rounded-full px-8 py-6 text-lg font-semibold"
|
||||
data-testid="back-to-dashboard-button"
|
||||
>
|
||||
<ArrowLeft className="mr-2 h-5 w-5" />
|
||||
@@ -92,17 +92,17 @@ const PaymentCancel = () => {
|
||||
</Card>
|
||||
|
||||
{/* Support Section */}
|
||||
<Card className="p-6 bg-gradient-to-br from-[#F2CC8F]/20 to-[#E07A5F]/20 rounded-2xl border border-[#EAE0D5]">
|
||||
<h3 className="text-lg font-semibold fraunces text-[#3D405B] mb-3 text-center">
|
||||
<Card className="p-6 bg-gradient-to-br from-[#DDD8EB]/20 to-[#f1eef9]/20 rounded-2xl border border-[#ddd8eb]">
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-3 text-center" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Need Assistance?
|
||||
</h3>
|
||||
<p className="text-[#6B708D] text-center mb-4">
|
||||
<p className="text-[#664fa3] text-center mb-4" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
If you encountered any technical issues or have questions about the payment process, our support team is here to help.
|
||||
</p>
|
||||
<div className="text-center">
|
||||
<a
|
||||
href="mailto:support@loaf.org"
|
||||
className="text-[#E07A5F] hover:text-[#D0694E] font-medium text-lg"
|
||||
className="text-[#ff9e77] hover:text-[#664fa3] font-medium text-lg"
|
||||
>
|
||||
support@loaf.org
|
||||
</a>
|
||||
|
||||
@@ -20,7 +20,7 @@ const PaymentSuccess = () => {
|
||||
}, [refreshUser]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#FDFCF8]">
|
||||
<div className="min-h-screen bg-white">
|
||||
<Navbar />
|
||||
|
||||
<div className="max-w-4xl mx-auto px-6 py-12">
|
||||
@@ -33,47 +33,47 @@ const PaymentSuccess = () => {
|
||||
</div>
|
||||
|
||||
{/* Success Message */}
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Payment Successful!
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D] max-w-2xl mx-auto mb-8">
|
||||
<p className="text-lg text-[#664fa3] max-w-2xl mx-auto mb-8" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Thank you for your payment. Your LOAF membership is now active!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Confirmation Card */}
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] shadow-lg mb-8">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6 text-center">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] shadow-lg mb-8">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-6 text-center" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Welcome to the LOAF Community!
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<div className="bg-[#FDFCF8] p-6 rounded-xl">
|
||||
<h3 className="text-lg font-semibold text-[#3D405B] mb-4">
|
||||
<div className="bg-[#f1eef9] p-6 rounded-xl">
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
What's Next?
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle className="h-5 w-5 text-[#81B29A] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#6B708D]">
|
||||
<span className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Your membership is now active and you have full access to all member benefits
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle className="h-5 w-5 text-[#81B29A] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#6B708D]">
|
||||
<span className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
You can now RSVP and attend members-only events
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle className="h-5 w-5 text-[#81B29A] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#6B708D]">
|
||||
<span className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Access the community directory and connect with other members
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<CheckCircle className="h-5 w-5 text-[#81B29A] flex-shrink-0 mt-0.5" />
|
||||
<span className="text-[#6B708D]">
|
||||
<span className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
You'll receive our newsletter with exclusive updates and announcements
|
||||
</span>
|
||||
</li>
|
||||
@@ -81,12 +81,12 @@ const PaymentSuccess = () => {
|
||||
</div>
|
||||
|
||||
{sessionId && (
|
||||
<div className="bg-[#F2CC8F]/20 p-4 rounded-xl">
|
||||
<p className="text-sm text-[#6B708D] text-center">
|
||||
<span className="font-medium text-[#3D405B]">Transaction ID:</span>{' '}
|
||||
<div className="bg-[#DDD8EB]/20 p-4 rounded-xl">
|
||||
<p className="text-sm text-[#664fa3] text-center" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<span className="font-medium text-[#422268]">Transaction ID:</span>{' '}
|
||||
<span className="font-mono text-xs">{sessionId}</span>
|
||||
</p>
|
||||
<p className="text-xs text-[#6B708D] text-center mt-2">
|
||||
<p className="text-xs text-[#664fa3] text-center mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
A confirmation email has been sent to your registered email address.
|
||||
</p>
|
||||
</div>
|
||||
@@ -97,7 +97,7 @@ const PaymentSuccess = () => {
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button
|
||||
onClick={() => navigate('/dashboard')}
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-8 py-6 text-lg font-semibold"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8 py-6 text-lg font-semibold"
|
||||
data-testid="go-to-dashboard-button"
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
@@ -106,7 +106,7 @@ const PaymentSuccess = () => {
|
||||
<Button
|
||||
onClick={() => navigate('/events')}
|
||||
variant="outline"
|
||||
className="border-2 border-[#E07A5F] text-[#E07A5F] hover:bg-[#E07A5F] hover:text-white rounded-full px-8 py-6 text-lg font-semibold"
|
||||
className="border-2 border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white rounded-full px-8 py-6 text-lg font-semibold"
|
||||
data-testid="browse-events-button"
|
||||
>
|
||||
<Calendar className="mr-2 h-5 w-5" />
|
||||
@@ -117,11 +117,11 @@ const PaymentSuccess = () => {
|
||||
|
||||
{/* Additional Info */}
|
||||
<div className="text-center">
|
||||
<p className="text-sm text-[#6B708D]">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Need help? Contact us at{' '}
|
||||
<a
|
||||
href="mailto:support@loaf.org"
|
||||
className="text-[#E07A5F] hover:text-[#D0694E] font-medium"
|
||||
className="text-[#ff9e77] hover:text-[#664fa3] font-medium"
|
||||
>
|
||||
support@loaf.org
|
||||
</a>
|
||||
|
||||
@@ -164,10 +164,10 @@ const AdminApprovals = () => {
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
const config = {
|
||||
pending_email: { label: 'Awaiting Email', className: 'bg-[#F2CC8F] text-[#3D405B]' },
|
||||
pending_approval: { label: 'Pending', className: 'bg-[#A3B1C6] text-white' },
|
||||
pending_email: { label: 'Awaiting Email', className: 'bg-orange-100 text-orange-700' },
|
||||
pending_approval: { label: 'Pending', className: 'bg-gray-200 text-gray-700' },
|
||||
pre_approved: { label: 'Pre-Approved', className: 'bg-[#81B29A] text-white' },
|
||||
payment_pending: { label: 'Payment Pending', className: 'bg-[#E07A5F] text-white' }
|
||||
payment_pending: { label: 'Payment Pending', className: 'bg-orange-500 text-white' }
|
||||
};
|
||||
|
||||
const statusConfig = config[status];
|
||||
@@ -205,44 +205,44 @@ const AdminApprovals = () => {
|
||||
<>
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Approval Queue
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Review and approve pending membership applications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Card */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] mb-8">
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div>
|
||||
<p className="text-sm text-[#6B708D] mb-2">Total Pending</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{pendingUsers.length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#6B708D] mb-2">Awaiting Email</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{pendingUsers.filter(u => u.status === 'pending_email').length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#6B708D] mb-2">Pending Approval</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Approval</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{pendingUsers.filter(u => u.status === 'pending_approval').length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#6B708D] mb-2">Pre-Approved</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<p className="text-sm text-[#664fa3] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pre-Approved</p>
|
||||
<p className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{pendingUsers.filter(u => u.status === 'pre_approved').length}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-[#6B708D] mb-2">Payment Pending</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{pendingUsers.filter(u => u.status === 'payment_pending').length}
|
||||
</p>
|
||||
</div>
|
||||
@@ -250,20 +250,20 @@ const AdminApprovals = () => {
|
||||
</Card>
|
||||
|
||||
{/* Filter Card */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#6B708D]" />
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<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-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#EAE0D5]">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectValue placeholder="Filter by status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -279,16 +279,16 @@ const AdminApprovals = () => {
|
||||
{/* Table */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#6B708D]">Loading pending applications...</p>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading pending applications...</p>
|
||||
</div>
|
||||
) : filteredUsers.length > 0 ? (
|
||||
<>
|
||||
<Card className="bg-white rounded-2xl border border-[#EAE0D5] overflow-hidden">
|
||||
<Card className="bg-white rounded-2xl border border-[#ddd8eb] overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-[#F2CC8F]/20"
|
||||
className="cursor-pointer hover:bg-[#DDD8EB]/20"
|
||||
onClick={() => handleSort('first_name')}
|
||||
>
|
||||
Name {renderSortIcon('first_name')}
|
||||
@@ -296,13 +296,13 @@ const AdminApprovals = () => {
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Phone</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-[#F2CC8F]/20"
|
||||
className="cursor-pointer hover:bg-[#DDD8EB]/20"
|
||||
onClick={() => handleSort('status')}
|
||||
>
|
||||
Status {renderSortIcon('status')}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer hover:bg-[#F2CC8F]/20"
|
||||
className="cursor-pointer hover:bg-[#DDD8EB]/20"
|
||||
onClick={() => handleSort('created_at')}
|
||||
>
|
||||
Registered {renderSortIcon('created_at')}
|
||||
@@ -333,7 +333,7 @@ const AdminApprovals = () => {
|
||||
onClick={() => handleBypassAndApprove(user.id)}
|
||||
disabled={actionLoading === user.id}
|
||||
size="sm"
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E]"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white"
|
||||
>
|
||||
{actionLoading === user.id ? 'Approving...' : 'Bypass & Approve'}
|
||||
</Button>
|
||||
@@ -341,7 +341,7 @@ const AdminApprovals = () => {
|
||||
<Button
|
||||
onClick={() => handleActivatePayment(user)}
|
||||
size="sm"
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E]"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white"
|
||||
>
|
||||
<CheckCircle className="h-4 w-4 mr-1" />
|
||||
Activate Payment
|
||||
@@ -368,7 +368,7 @@ const AdminApprovals = () => {
|
||||
<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-[#6B708D]">Show</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Show</p>
|
||||
<Select
|
||||
value={itemsPerPage.toString()}
|
||||
onValueChange={(val) => {
|
||||
@@ -386,7 +386,7 @@ const AdminApprovals = () => {
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-[#6B708D]">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
entries (showing {(currentPage - 1) * itemsPerPage + 1}-
|
||||
{Math.min(currentPage * itemsPerPage, filteredUsers.length)} of {filteredUsers.length})
|
||||
</p>
|
||||
@@ -443,11 +443,11 @@ const AdminApprovals = () => {
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Clock className="h-20 w-20 text-[#EAE0D5] mx-auto mb-6" />
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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" }}>
|
||||
No Pending Approvals
|
||||
</h3>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery || statusFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'All applications have been reviewed!'}
|
||||
|
||||
@@ -40,66 +40,66 @@ const AdminDashboard = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Admin Dashboard
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage users, events, and membership applications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 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-[#EAE0D5]" data-testid="stat-total-users">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]" data-testid="stat-total-users">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="bg-[#A3B1C6]/20 p-3 rounded-lg">
|
||||
<Users className="h-6 w-6 text-[#A3B1C6]" />
|
||||
<div className="bg-[#DDD8EB]/20 p-3 rounded-lg">
|
||||
<Users className="h-6 w-6 text-[#664fa3]" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B] mb-1">
|
||||
<p className="text-3xl font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{loading ? '-' : stats.totalMembers}
|
||||
</p>
|
||||
<p className="text-sm text-[#6B708D]">Total Members</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Total Members</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]" data-testid="stat-pending-approvals">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]" data-testid="stat-pending-approvals">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="bg-[#F2CC8F]/20 p-3 rounded-lg">
|
||||
<Clock className="h-6 w-6 text-[#E07A5F]" />
|
||||
<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 fraunces text-[#3D405B] mb-1">
|
||||
<p className="text-3xl font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{loading ? '-' : stats.pendingApprovals}
|
||||
</p>
|
||||
<p className="text-sm text-[#6B708D]">Pending Approvals</p>
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Pending Approvals</p>
|
||||
</Card>
|
||||
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]" data-testid="stat-active-members">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb]" 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>
|
||||
</div>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B] mb-1">
|
||||
<p className="text-3xl font-semibold text-[#422268] mb-1" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{loading ? '-' : stats.activeMembers}
|
||||
</p>
|
||||
<p className="text-sm text-[#6B708D]">Active Members</p>
|
||||
<p className="text-sm text-[#664fa3]" 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/users">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] hover:shadow-lg hover:-translate-y-1 transition-all cursor-pointer" data-testid="quick-action-users">
|
||||
<Users className="h-12 w-12 text-[#E07A5F] mb-4" />
|
||||
<h3 className="text-xl font-semibold fraunces text-[#3D405B] mb-2">
|
||||
<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" }}>
|
||||
Manage Users
|
||||
</h3>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
View and manage all registered users and their membership status.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4 bg-[#F2CC8F] text-[#3D405B] hover:bg-[#E5B875] rounded-full"
|
||||
className="mt-4 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
data-testid="manage-users-button"
|
||||
>
|
||||
Go to Users
|
||||
@@ -108,16 +108,16 @@ const AdminDashboard = () => {
|
||||
</Link>
|
||||
|
||||
<Link to="/admin/approvals">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] hover:shadow-lg hover:-translate-y-1 transition-all cursor-pointer" data-testid="quick-action-approvals">
|
||||
<Clock className="h-12 w-12 text-[#E07A5F] mb-4" />
|
||||
<h3 className="text-xl font-semibold fraunces text-[#3D405B] mb-2">
|
||||
<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-approvals">
|
||||
<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" }}>
|
||||
Approval Queue
|
||||
</h3>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Review and approve pending membership applications.
|
||||
</p>
|
||||
<Button
|
||||
className="mt-4 bg-[#F2CC8F] text-[#3D405B] hover:bg-[#E5B875] rounded-full"
|
||||
className="mt-4 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
data-testid="manage-approvals-button"
|
||||
>
|
||||
View Approvals
|
||||
|
||||
@@ -133,10 +133,10 @@ const AdminEvents = () => {
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex justify-between items-center">
|
||||
<div>
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Event Management
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Create and manage community events.
|
||||
</p>
|
||||
</div>
|
||||
@@ -148,7 +148,7 @@ const AdminEvents = () => {
|
||||
resetForm();
|
||||
setEditingEvent(null);
|
||||
}}
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-6"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||
data-testid="create-event-button"
|
||||
>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
@@ -158,39 +158,39 @@ const AdminEvents = () => {
|
||||
|
||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-2xl fraunces text-[#3D405B]">
|
||||
<DialogTitle className="text-2xl text-[#422268]" 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-[#3D405B] mb-2">
|
||||
<label className="block text-sm font-medium text-[#422268] 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-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#3D405B] mb-2">
|
||||
<label className="block text-sm font-medium text-[#422268] 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-[#EAE0D5] focus:border-[#E07A5F] rounded-lg p-3"
|
||||
className="w-full border-2 border-[#ddd8eb] focus:border-[#664fa3] rounded-lg p-3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#3D405B] mb-2">
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Start Date & Time *
|
||||
</label>
|
||||
<Input
|
||||
@@ -198,12 +198,12 @@ const AdminEvents = () => {
|
||||
value={formData.start_at}
|
||||
onChange={(e) => setFormData({ ...formData, start_at: e.target.value })}
|
||||
required
|
||||
className="border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#3D405B] mb-2">
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
End Date & Time *
|
||||
</label>
|
||||
<Input
|
||||
@@ -211,25 +211,25 @@ const AdminEvents = () => {
|
||||
value={formData.end_at}
|
||||
onChange={(e) => setFormData({ ...formData, end_at: e.target.value })}
|
||||
required
|
||||
className="border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#3D405B] mb-2">
|
||||
<label className="block text-sm font-medium text-[#422268] 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-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[#3D405B] mb-2">
|
||||
<label className="block text-sm font-medium text-[#422268] mb-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Capacity (optional)
|
||||
</label>
|
||||
<Input
|
||||
@@ -237,7 +237,7 @@ const AdminEvents = () => {
|
||||
value={formData.capacity}
|
||||
onChange={(e) => setFormData({ ...formData, capacity: e.target.value })}
|
||||
placeholder="Leave empty for unlimited"
|
||||
className="border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -247,9 +247,9 @@ const AdminEvents = () => {
|
||||
id="published"
|
||||
checked={formData.published}
|
||||
onChange={(e) => setFormData({ ...formData, published: e.target.checked })}
|
||||
className="w-4 h-4 text-[#E07A5F] border-[#EAE0D5] rounded focus:ring-[#E07A5F]"
|
||||
className="w-4 h-4 text-[#664fa3] border-[#ddd8eb] rounded focus:ring-[#664fa3]"
|
||||
/>
|
||||
<label htmlFor="published" className="text-sm font-medium text-[#3D405B]">
|
||||
<label htmlFor="published" className="text-sm font-medium text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Publish event (make visible to members)
|
||||
</label>
|
||||
</div>
|
||||
@@ -257,7 +257,7 @@ const AdminEvents = () => {
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Button
|
||||
type="submit"
|
||||
className="flex-1 bg-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full"
|
||||
className="flex-1 bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full"
|
||||
>
|
||||
{editingEvent ? 'Update Event' : 'Create Event'}
|
||||
</Button>
|
||||
@@ -265,7 +265,7 @@ const AdminEvents = () => {
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleDialogClose}
|
||||
className="flex-1 border-2 border-[#6B708D] text-[#6B708D] hover:bg-[#6B708D] hover:text-white rounded-full"
|
||||
className="flex-1 border-2 border-gray-400 text-gray-600 hover:bg-gray-400 hover:text-white rounded-full"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -278,26 +278,26 @@ const AdminEvents = () => {
|
||||
{/* Events List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#6B708D]">Loading events...</p>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading events...</p>
|
||||
</div>
|
||||
) : events.length > 0 ? (
|
||||
<div className="grid md: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-[#EAE0D5] hover:shadow-lg transition-all"
|
||||
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-[#F2CC8F]/20 p-3 rounded-lg">
|
||||
<Calendar className="h-6 w-6 text-[#E07A5F]" />
|
||||
<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-[#6B708D] text-white'
|
||||
: 'bg-gray-400 text-white'
|
||||
} px-3 py-1 rounded-full`}
|
||||
>
|
||||
{event.published ? 'Published' : 'Draft'}
|
||||
@@ -305,18 +305,18 @@ const AdminEvents = () => {
|
||||
</div>
|
||||
|
||||
{/* Event Details */}
|
||||
<h3 className="text-xl font-semibold fraunces text-[#3D405B] mb-3">
|
||||
<h3 className="text-xl font-semibold text-[#422268] mb-3" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{event.title}
|
||||
</h3>
|
||||
|
||||
{event.description && (
|
||||
<p className="text-[#6B708D] mb-4 line-clamp-2 text-sm">
|
||||
<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-[#6B708D]">
|
||||
<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{' '}
|
||||
@@ -326,18 +326,18 @@ const AdminEvents = () => {
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm text-[#6B708D]">
|
||||
<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-[#6B708D]">
|
||||
<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>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="space-y-2 pt-4 border-t border-[#EAE0D5]">
|
||||
<div className="space-y-2 pt-4 border-t border-[#ddd8eb]">
|
||||
{/* Mark Attendance Button */}
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -359,7 +359,7 @@ const AdminEvents = () => {
|
||||
onClick={() => togglePublish(event)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 border-[#E07A5F] text-[#E07A5F] hover:bg-[#E07A5F] hover:text-white"
|
||||
className="flex-1 border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white"
|
||||
data-testid={`toggle-publish-${event.id}`}
|
||||
>
|
||||
{event.published ? (
|
||||
@@ -378,7 +378,7 @@ const AdminEvents = () => {
|
||||
onClick={() => handleEdit(event)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-[#6B708D] text-[#6B708D] hover:bg-[#6B708D] hover:text-white"
|
||||
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" />
|
||||
@@ -399,16 +399,16 @@ const AdminEvents = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Calendar className="h-20 w-20 text-[#EAE0D5] mx-auto mb-6" />
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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-[#6B708D] mb-6">
|
||||
<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-[#E07A5F] text-white hover:bg-[#D0694E] rounded-full px-8"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8"
|
||||
>
|
||||
<Plus className="mr-2 h-5 w-5" />
|
||||
Create Event
|
||||
|
||||
@@ -72,12 +72,12 @@ const AdminMembers = () => {
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
const config = {
|
||||
pending_email: { label: 'Pending Email', className: 'bg-[#F2CC8F] text-[#3D405B]' },
|
||||
pending_approval: { label: 'Pending Approval', className: 'bg-[#A3B1C6] text-white' },
|
||||
pending_email: { label: 'Pending Email', className: 'bg-orange-100 text-orange-700' },
|
||||
pending_approval: { label: 'Pending Approval', className: 'bg-gray-200 text-gray-700' },
|
||||
pre_approved: { label: 'Pre-Approved', className: 'bg-[#81B29A] text-white' },
|
||||
payment_pending: { label: 'Payment Pending', className: 'bg-[#E07A5F] text-white' },
|
||||
payment_pending: { label: 'Payment Pending', className: 'bg-orange-500 text-white' },
|
||||
active: { label: 'Active', className: 'bg-[#81B29A] text-white' },
|
||||
inactive: { label: 'Inactive', className: 'bg-[#6B708D] text-white' }
|
||||
inactive: { label: 'Inactive', className: 'bg-gray-400 text-white' }
|
||||
};
|
||||
|
||||
const statusConfig = config[status] || config.inactive;
|
||||
@@ -91,57 +91,57 @@ const AdminMembers = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Members Management
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage paying members and their subscriptions.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid md:grid-cols-4 gap-4 mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Total Members</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Active</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.filter(u => u.status === 'active').length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Payment Pending</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.filter(u => u.status === 'payment_pending').length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Inactive</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.filter(u => u.status === 'inactive').length}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#6B708D]" />
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<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-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="search-members-input"
|
||||
/>
|
||||
</div>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#EAE0D5]" data-testid="status-filter-select">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]" data-testid="status-filter-select">
|
||||
<SelectValue placeholder="Filter by status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -159,32 +159,32 @@ const AdminMembers = () => {
|
||||
{/* Members List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#6B708D]">Loading members...</p>
|
||||
<p className="text-[#664fa3]" 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-[#EAE0D5] hover:shadow-md transition-shadow"
|
||||
className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#F2CC8F] flex items-center justify-center text-[#3D405B] font-semibold text-lg flex-shrink-0">
|
||||
<div className="h-14 w-14 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] 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 fraunces text-[#3D405B]">
|
||||
<h3 className="text-xl font-semibold text-[#422268]" 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-[#6B708D]">
|
||||
<div className="grid md:grid-cols-2 gap-2 text-sm text-[#664fa3]" 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>
|
||||
@@ -201,7 +201,7 @@ const AdminMembers = () => {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="border-[#A3B1C6] text-[#A3B1C6] hover:bg-[#A3B1C6] hover:text-white"
|
||||
className="border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
View Profile
|
||||
@@ -213,7 +213,7 @@ const AdminMembers = () => {
|
||||
<Button
|
||||
onClick={() => handleActivatePayment(user)}
|
||||
size="sm"
|
||||
className="bg-[#E07A5F] text-white hover:bg-[#D0694E]"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white"
|
||||
>
|
||||
<CheckCircle className="h-4 w-4 mr-1" />
|
||||
Activate Payment
|
||||
@@ -238,11 +238,11 @@ const AdminMembers = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Users className="h-20 w-20 text-[#EAE0D5] mx-auto mb-6" />
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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" }}>
|
||||
No Members Found
|
||||
</h3>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery || statusFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'No members yet'}
|
||||
|
||||
@@ -118,16 +118,16 @@ 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 fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Subscription Plans
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage membership plans and pricing.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleCreatePlan}
|
||||
className="bg-[#E07A5F] hover:bg-[#D0694E] text-white rounded-full px-6"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create Plan
|
||||
@@ -137,27 +137,27 @@ const AdminPlans = () => {
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid md:grid-cols-4 gap-4 mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Total Plans</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{plans.length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Active Plans</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{plans.filter(p => p.active).length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Total Subscribers</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{plans.reduce((sum, p) => sum + (p.subscriber_count || 0), 0)}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Revenue (Annual Est.)</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{formatPrice(
|
||||
plans.reduce((sum, p) => {
|
||||
const annualPrice = p.billing_cycle === 'yearly'
|
||||
@@ -171,19 +171,19 @@ const AdminPlans = () => {
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#6B708D]" />
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Input
|
||||
placeholder="Search plans..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-12 h-14 rounded-xl border-2 border-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
/>
|
||||
</div>
|
||||
<Select value={activeFilter} onValueChange={setActiveFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#EAE0D5]">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]">
|
||||
<SelectValue placeholder="Filter by status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -198,7 +198,7 @@ const AdminPlans = () => {
|
||||
{/* Plans Grid */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#6B708D]">Loading plans...</p>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading plans...</p>
|
||||
</div>
|
||||
) : filteredPlans.length > 0 ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
@@ -207,8 +207,8 @@ const AdminPlans = () => {
|
||||
key={plan.id}
|
||||
className={`p-6 bg-white rounded-2xl border-2 transition-all hover:shadow-lg ${
|
||||
plan.active
|
||||
? 'border-[#EAE0D5] hover:border-[#E07A5F]'
|
||||
: 'border-[#6B708D] opacity-60'
|
||||
? 'border-[#ddd8eb] hover:border-[#664fa3]'
|
||||
: 'border-gray-400 opacity-60'
|
||||
}`}
|
||||
>
|
||||
{/* Header with badges */}
|
||||
@@ -217,13 +217,13 @@ const AdminPlans = () => {
|
||||
className={`${
|
||||
plan.active
|
||||
? 'bg-[#81B29A] text-white'
|
||||
: 'bg-[#6B708D] text-white'
|
||||
: 'bg-gray-400 text-white'
|
||||
}`}
|
||||
>
|
||||
{plan.active ? 'Active' : 'Inactive'}
|
||||
</Badge>
|
||||
{plan.subscriber_count > 0 && (
|
||||
<Badge className="bg-[#F2CC8F] text-[#3D405B]">
|
||||
<Badge className="bg-[#DDD8EB] text-[#422268]">
|
||||
<Users className="h-3 w-3 mr-1" />
|
||||
{plan.subscriber_count}
|
||||
</Badge>
|
||||
@@ -231,23 +231,23 @@ const AdminPlans = () => {
|
||||
</div>
|
||||
|
||||
{/* Plan Name */}
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-2">
|
||||
<h3 className="text-2xl font-semibold text-[#422268] mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{plan.name}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
{plan.description && (
|
||||
<p className="text-sm text-[#6B708D] mb-4 line-clamp-2">
|
||||
<p className="text-sm text-[#664fa3] mb-4 line-clamp-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{plan.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Price */}
|
||||
<div className="mb-4">
|
||||
<div className="text-3xl font-bold fraunces text-[#E07A5F]">
|
||||
<div className="text-3xl font-bold text-[#ff9e77]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{formatPrice(plan.price_cents)}
|
||||
</div>
|
||||
<p className="text-sm text-[#6B708D]">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{getBillingCycleLabel(plan.billing_cycle)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -260,19 +260,19 @@ const AdminPlans = () => {
|
||||
Stripe Integrated
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-[#F2CC8F] text-[#3D405B] text-xs">
|
||||
<Badge className="bg-[#DDD8EB] text-[#422268] text-xs">
|
||||
Manual/Test Plan
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 pt-4 border-t border-[#EAE0D5]">
|
||||
<div className="flex gap-2 pt-4 border-t border-[#ddd8eb]">
|
||||
<Button
|
||||
onClick={() => handleEditPlan(plan)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 border-[#A3B1C6] text-[#A3B1C6] hover:bg-[#A3B1C6] hover:text-white rounded-full"
|
||||
className="flex-1 border-[#664fa3] text-[#664fa3] hover:bg-[#664fa3] hover:text-white rounded-full"
|
||||
>
|
||||
<Edit className="h-4 w-4 mr-1" />
|
||||
Edit
|
||||
@@ -281,7 +281,7 @@ const AdminPlans = () => {
|
||||
onClick={() => handleDeleteClick(plan)}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1 border-[#E07A5F] text-[#E07A5F] hover:bg-[#E07A5F] hover:text-white rounded-full"
|
||||
className="flex-1 border-red-500 text-red-500 hover:bg-red-500 hover:text-white rounded-full"
|
||||
disabled={plan.subscriber_count > 0}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-1" />
|
||||
@@ -291,7 +291,7 @@ const AdminPlans = () => {
|
||||
|
||||
{/* Warning for plans with subscribers */}
|
||||
{plan.subscriber_count > 0 && (
|
||||
<p className="text-xs text-[#6B708D] mt-2 text-center">
|
||||
<p className="text-xs text-[#664fa3] mt-2 text-center" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Cannot delete plan with active subscribers
|
||||
</p>
|
||||
)}
|
||||
@@ -300,11 +300,11 @@ const AdminPlans = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<CreditCard className="h-20 w-20 text-[#EAE0D5] mx-auto mb-6" />
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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" }}>
|
||||
No Plans Found
|
||||
</h3>
|
||||
<p className="text-[#6B708D] mb-6">
|
||||
<p className="text-[#664fa3] mb-6" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery || activeFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'Create your first subscription plan to get started'}
|
||||
@@ -312,7 +312,7 @@ const AdminPlans = () => {
|
||||
{!searchQuery && activeFilter === 'all' && (
|
||||
<Button
|
||||
onClick={handleCreatePlan}
|
||||
className="bg-[#E07A5F] hover:bg-[#D0694E] text-white rounded-full px-8"
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-8"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Create First Plan
|
||||
@@ -333,10 +333,10 @@ const AdminPlans = () => {
|
||||
{deleteDialogOpen && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<Card className="p-8 bg-white rounded-2xl max-w-md mx-4">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h2 className="text-2xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Delete Plan
|
||||
</h2>
|
||||
<p className="text-[#6B708D] mb-6">
|
||||
<p className="text-[#664fa3] 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>
|
||||
@@ -350,7 +350,7 @@ const AdminPlans = () => {
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDeleteConfirm}
|
||||
className="flex-1 bg-[#E07A5F] hover:bg-[#D0694E] text-white"
|
||||
className="flex-1 bg-red-500 hover:bg-red-600 text-white"
|
||||
>
|
||||
Delete Plan
|
||||
</Button>
|
||||
|
||||
@@ -62,11 +62,11 @@ const AdminStaff = () => {
|
||||
|
||||
const getRoleBadge = (role) => {
|
||||
const config = {
|
||||
superadmin: { label: 'Superadmin', className: 'bg-[#E07A5F] text-white' },
|
||||
superadmin: { label: 'Superadmin', className: 'bg-[#664fa3] text-white' },
|
||||
admin: { label: 'Admin', className: 'bg-[#81B29A] text-white' },
|
||||
moderator: { label: 'Moderator', className: 'bg-[#A3B1C6] text-white' },
|
||||
staff: { label: 'Staff', className: 'bg-[#F2CC8F] text-[#3D405B]' },
|
||||
media: { label: 'Media', className: 'bg-[#6B708D] text-white' }
|
||||
moderator: { label: 'Moderator', className: 'bg-[#DDD8EB] text-[#422268]' },
|
||||
staff: { label: 'Staff', className: 'bg-gray-200 text-gray-700' },
|
||||
media: { label: 'Media', className: 'bg-gray-400 text-white' }
|
||||
};
|
||||
|
||||
const roleConfig = config[role] || { label: role, className: 'bg-gray-500 text-white' };
|
||||
@@ -81,7 +81,7 @@ const AdminStaff = () => {
|
||||
const getStatusBadge = (status) => {
|
||||
const config = {
|
||||
active: { label: 'Active', className: 'bg-[#81B29A] text-white' },
|
||||
inactive: { label: 'Inactive', className: 'bg-[#6B708D] text-white' }
|
||||
inactive: { label: 'Inactive', className: 'bg-gray-400 text-white' }
|
||||
};
|
||||
|
||||
const statusConfig = config[status] || config.inactive;
|
||||
@@ -95,57 +95,57 @@ const AdminStaff = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Staff Management
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Manage internal team members and their roles.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid md:grid-cols-4 gap-4 mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Total Staff</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Admins</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.filter(u => ['admin', 'superadmin'].includes(u.role)).length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Moderators</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.filter(u => u.role === 'moderator').length}
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<p className="text-sm text-[#6B708D] mb-2">Active</p>
|
||||
<p className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<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" }}>
|
||||
{users.filter(u => u.status === 'active').length}
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#6B708D]" />
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<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-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="search-staff-input"
|
||||
/>
|
||||
</div>
|
||||
<Select value={roleFilter} onValueChange={setRoleFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#EAE0D5]" data-testid="role-filter-select">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]" data-testid="role-filter-select">
|
||||
<SelectValue placeholder="Filter by role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -163,33 +163,33 @@ const AdminStaff = () => {
|
||||
{/* Staff List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#6B708D]">Loading staff...</p>
|
||||
<p className="text-[#664fa3]" 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-[#EAE0D5] hover:shadow-md transition-shadow"
|
||||
className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#F2CC8F] flex items-center justify-center text-[#3D405B] font-semibold text-lg flex-shrink-0">
|
||||
<div className="h-14 w-14 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] 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 fraunces text-[#3D405B]">
|
||||
<h3 className="text-xl font-semibold text-[#422268]" 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-[#6B708D]">
|
||||
<div className="grid md:grid-cols-2 gap-2 text-sm text-[#664fa3]" 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>
|
||||
@@ -205,11 +205,11 @@ const AdminStaff = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<UserCog className="h-20 w-20 text-[#EAE0D5] mx-auto mb-6" />
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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" }}>
|
||||
No Staff Found
|
||||
</h3>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery || roleFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'No staff members yet'}
|
||||
|
||||
@@ -93,17 +93,17 @@ const AdminUserView = () => {
|
||||
</Button>
|
||||
|
||||
{/* User Profile Header */}
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#ddd8eb] mb-8">
|
||||
<div className="flex items-start gap-6">
|
||||
{/* Avatar */}
|
||||
<div className="h-24 w-24 rounded-full bg-[#F2CC8F] flex items-center justify-center text-[#3D405B] font-semibold text-3xl">
|
||||
<div className="h-24 w-24 rounded-full bg-[#DDD8EB] flex items-center justify-center text-[#422268] font-semibold text-3xl">
|
||||
{user.first_name?.[0]}{user.last_name?.[0]}
|
||||
</div>
|
||||
|
||||
{/* User Info */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<h1 className="text-3xl font-semibold fraunces text-[#3D405B]">
|
||||
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
{user.first_name} {user.last_name}
|
||||
</h1>
|
||||
{/* Status & Role Badges */}
|
||||
@@ -112,7 +112,7 @@ const AdminUserView = () => {
|
||||
</div>
|
||||
|
||||
{/* Contact Info */}
|
||||
<div className="grid md:grid-cols-2 gap-4 text-[#6B708D]">
|
||||
<div className="grid md:grid-cols-2 gap-4 text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="h-4 w-4" />
|
||||
<span>{user.email}</span>
|
||||
@@ -135,8 +135,8 @@ const AdminUserView = () => {
|
||||
</Card>
|
||||
|
||||
{/* Admin Actions */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<h2 className="text-lg font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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" }}>
|
||||
Admin Actions
|
||||
</h2>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
@@ -144,7 +144,7 @@ const AdminUserView = () => {
|
||||
onClick={handleResetPassword}
|
||||
disabled={resetPasswordLoading}
|
||||
variant="outline"
|
||||
className="border-2 border-[#E07A5F] text-[#E07A5F] hover:bg-[#FFF3E0] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
className="border-2 border-[#664fa3] text-[#664fa3] hover:bg-[#f1eef9] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
>
|
||||
<Lock className="h-4 w-4 mr-2" />
|
||||
{resetPasswordLoading ? 'Resetting...' : 'Reset Password'}
|
||||
@@ -155,14 +155,14 @@ const AdminUserView = () => {
|
||||
onClick={handleResendVerification}
|
||||
disabled={resendVerificationLoading}
|
||||
variant="outline"
|
||||
className="border-2 border-[#E07A5F] text-[#E07A5F] hover:bg-[#FFF3E0] rounded-full px-4 py-2 disabled:opacity-50"
|
||||
className="border-2 border-[#ff9e77] text-[#ff9e77] 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'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 text-sm text-[#6B708D] ml-2">
|
||||
<div className="flex items-center gap-2 text-sm text-[#664fa3] 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>
|
||||
@@ -170,28 +170,28 @@ const AdminUserView = () => {
|
||||
</Card>
|
||||
|
||||
{/* Additional Details */}
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5]">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6">
|
||||
<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" }}>
|
||||
Additional Information
|
||||
</h2>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#6B708D]">Address</label>
|
||||
<p className="text-[#3D405B] mt-1">{user.address}</p>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#6B708D]">Date of Birth</label>
|
||||
<p className="text-[#3D405B] mt-1">
|
||||
<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" }}>
|
||||
{new Date(user.date_of_birth).toLocaleDateString()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{user.partner_first_name && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#6B708D]">Partner</label>
|
||||
<p className="text-[#3D405B] mt-1">
|
||||
<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" }}>
|
||||
{user.partner_first_name} {user.partner_last_name}
|
||||
</p>
|
||||
</div>
|
||||
@@ -199,14 +199,14 @@ const AdminUserView = () => {
|
||||
|
||||
{user.referred_by_member_name && (
|
||||
<div>
|
||||
<label className="text-sm font-medium text-[#6B708D]">Referred By</label>
|
||||
<p className="text-[#3D405B] mt-1">{user.referred_by_member_name}</p>
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{user.lead_sources && user.lead_sources.length > 0 && (
|
||||
<div className="md:col-span-2">
|
||||
<label className="text-sm font-medium text-[#6B708D]">Lead Sources</label>
|
||||
<label className="text-sm font-medium text-[#664fa3]" 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>
|
||||
@@ -219,12 +219,12 @@ const AdminUserView = () => {
|
||||
|
||||
{/* Subscription Info (if applicable) */}
|
||||
{user.role === 'member' && (
|
||||
<Card className="p-8 bg-white rounded-2xl border border-[#EAE0D5] mt-8">
|
||||
<h2 className="text-2xl font-semibold fraunces text-[#3D405B] mb-6">
|
||||
<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" }}>
|
||||
Subscription Information
|
||||
</h2>
|
||||
{/* TODO: Fetch and display subscription data */}
|
||||
<p className="text-[#6B708D]">Subscription details coming soon...</p>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Subscription details coming soon...</p>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -59,12 +59,12 @@ const AdminUsers = () => {
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
const config = {
|
||||
pending_email: { label: 'Pending Email', className: 'bg-[#F2CC8F] text-[#3D405B]' },
|
||||
pending_approval: { label: 'Pending Approval', className: 'bg-[#A3B1C6] text-white' },
|
||||
pending_email: { label: 'Pending Email', className: 'bg-orange-100 text-orange-700' },
|
||||
pending_approval: { label: 'Pending Approval', className: 'bg-gray-200 text-gray-700' },
|
||||
pre_approved: { label: 'Pre-Approved', className: 'bg-[#81B29A] text-white' },
|
||||
payment_pending: { label: 'Payment Pending', className: 'bg-[#E07A5F] text-white' },
|
||||
payment_pending: { label: 'Payment Pending', className: 'bg-orange-500 text-white' },
|
||||
active: { label: 'Active', className: 'bg-[#81B29A] text-white' },
|
||||
inactive: { label: 'Inactive', className: 'bg-[#6B708D] text-white' }
|
||||
inactive: { label: 'Inactive', className: 'bg-gray-400 text-white' }
|
||||
};
|
||||
|
||||
const statusConfig = config[status] || config.inactive;
|
||||
@@ -97,29 +97,29 @@ const AdminUsers = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
User Management
|
||||
</h1>
|
||||
<p className="text-lg text-[#6B708D]">
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
View and manage all registered users.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Filters */}
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#EAE0D5] mb-8">
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] 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-[#6B708D]" />
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<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-[#EAE0D5] focus:border-[#E07A5F]"
|
||||
className="pl-12 h-14 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
|
||||
data-testid="search-users-input"
|
||||
/>
|
||||
</div>
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#EAE0D5]" data-testid="status-filter-select">
|
||||
<SelectTrigger className="h-14 rounded-xl border-2 border-[#ddd8eb]" data-testid="status-filter-select">
|
||||
<SelectValue placeholder="Filter by status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -138,25 +138,25 @@ const AdminUsers = () => {
|
||||
{/* Users List */}
|
||||
{loading ? (
|
||||
<div className="text-center py-20">
|
||||
<p className="text-[#6B708D]">Loading users...</p>
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>Loading users...</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-[#EAE0D5] hover:shadow-md transition-shadow"
|
||||
className="p-6 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-md transition-shadow"
|
||||
data-testid={`user-card-${user.id}`}
|
||||
>
|
||||
<div className="flex justify-between items-start flex-wrap gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h3 className="text-xl font-semibold fraunces text-[#3D405B]">
|
||||
<h3 className="text-xl font-semibold text-[#422268]" 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-[#6B708D]">
|
||||
<div className="grid md:grid-cols-2 gap-2 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<p>Email: {user.email}</p>
|
||||
<p>Phone: {user.phone}</p>
|
||||
<p>Role: <span className="capitalize">{user.role}</span></p>
|
||||
@@ -171,7 +171,7 @@ const AdminUsers = () => {
|
||||
onClick={() => navigate(`/admin/users/${user.id}`)}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#6B708D] hover:text-[#3D405B]"
|
||||
className="text-[#664fa3] hover:text-[#422268]"
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
View
|
||||
@@ -183,7 +183,7 @@ const AdminUsers = () => {
|
||||
disabled={resendingUserId === user.id}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#E07A5F] hover:text-[#D0694E]"
|
||||
className="text-[#ff9e77] hover:text-[#664fa3]"
|
||||
>
|
||||
<Mail className="h-4 w-4 mr-1" />
|
||||
{resendingUserId === user.id ? 'Sending...' : 'Resend Verification'}
|
||||
@@ -196,11 +196,11 @@ const AdminUsers = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-20">
|
||||
<Users className="h-20 w-20 text-[#EAE0D5] mx-auto mb-6" />
|
||||
<h3 className="text-2xl font-semibold fraunces text-[#3D405B] mb-4">
|
||||
<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" }}>
|
||||
No Users Found
|
||||
</h3>
|
||||
<p className="text-[#6B708D]">
|
||||
<p className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
{searchQuery || statusFilter !== 'all'
|
||||
? 'Try adjusting your filters'
|
||||
: 'No users registered yet'}
|
||||
|
||||
Reference in New Issue
Block a user