Update New Features

This commit is contained in:
Koncept Kit
2025-12-10 17:52:47 +07:00
parent 1f27c3224b
commit 36017e8693
39 changed files with 4977 additions and 196 deletions

View File

@@ -0,0 +1,216 @@
import React, { useState } from 'react';
import { Calendar, ChevronDown, Download, RefreshCw } from 'lucide-react';
import { Button } from './ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from './ui/dropdown-menu';
/**
* AddToCalendarButton Component
* Universal calendar export button with support for:
* - Google Calendar (web link)
* - Microsoft Outlook (web link)
* - Apple Calendar / Outlook Desktop (webcal:// subscription)
* - Download .ics file (universal import)
* - Subscribe to personal feed (auto-sync)
*/
export default function AddToCalendarButton({
event = null, // Single event object { id, title, description, start_at, end_at, location }
showSubscribe = false, // Show "Subscribe to My Events" option
variant = "default", // Button variant: default, outline, ghost
size = "default" // Button size: default, sm, lg
}) {
const [isOpen, setIsOpen] = useState(false);
const backendUrl = process.env.REACT_APP_BACKEND_URL || 'http://localhost:8000';
// Format datetime for Google Calendar (YYYYMMDDTHHMMSSZ)
const formatGoogleDate = (dateString) => {
const date = new Date(dateString);
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
};
// Generate Google Calendar URL
const getGoogleCalendarUrl = () => {
if (!event) return null;
const params = new URLSearchParams({
action: 'TEMPLATE',
text: event.title,
dates: `${formatGoogleDate(event.start_at)}/${formatGoogleDate(event.end_at)}`,
details: event.description || '',
location: event.location || '',
});
return `https://calendar.google.com/calendar/render?${params.toString()}`;
};
// Generate Microsoft Outlook Web URL
const getOutlookWebUrl = () => {
if (!event) return null;
const startDate = new Date(event.start_at).toISOString();
const endDate = new Date(event.end_at).toISOString();
const params = new URLSearchParams({
path: '/calendar/action/compose',
rru: 'addevent',
subject: event.title,
startdt: startDate,
enddt: endDate,
body: event.description || '',
location: event.location || '',
});
return `https://outlook.live.com/calendar/0/deeplink/compose?${params.toString()}`;
};
// Get .ics download URL
const getIcsDownloadUrl = () => {
if (!event) return null;
return `${backendUrl}/api/events/${event.id}/download.ics`;
};
// Get webcal:// subscription URL (for Apple Calendar / Outlook Desktop)
const getWebcalUrl = () => {
// Convert http:// to webcal://
const webcalUrl = backendUrl.replace(/^https?:\/\//, 'webcal://');
return `${webcalUrl}/api/calendars/subscribe.ics`;
};
// Get all events download URL
const getAllEventsUrl = () => {
return `${backendUrl}/api/calendars/all-events.ics`;
};
// Handle calendar action
const handleCalendarAction = (action) => {
setIsOpen(false);
switch (action) {
case 'google':
window.open(getGoogleCalendarUrl(), '_blank');
break;
case 'outlook':
window.open(getOutlookWebUrl(), '_blank');
break;
case 'apple':
window.location.href = getWebcalUrl();
break;
case 'download':
window.open(getIcsDownloadUrl(), '_blank');
break;
case 'subscribe':
window.location.href = getWebcalUrl();
break;
case 'all-events':
window.open(getAllEventsUrl(), '_blank');
break;
default:
break;
}
};
return (
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger asChild>
<Button variant={variant} size={size} className="gap-2">
<Calendar className="h-4 w-4" />
Add to Calendar
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64">
{event && (
<>
{/* Single Event Export Options */}
<div className="px-2 py-1.5 text-sm font-semibold text-[#422268]">
Add This Event
</div>
<DropdownMenuItem
onClick={() => handleCalendarAction('google')}
className="cursor-pointer"
>
<svg className="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>
</svg>
Google Calendar
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleCalendarAction('outlook')}
className="cursor-pointer"
>
<svg className="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
<path d="M7 2h14v20H7V2zm7 11c0 2.761-2.239 5-5 5H2c-.552 0-1-.448-1-1s.448-1 1-1h7c1.657 0 3-1.343 3-3V9c0-1.657-1.343-3-3-3H2c-.552 0-1-.448-1-1s.448-1 1-1h7c2.761 0 5 2.239 5 5v4z"/>
</svg>
Outlook Web
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleCalendarAction('apple')}
className="cursor-pointer"
>
<svg className="h-4 w-4 mr-2" viewBox="0 0 24 24" fill="currentColor">
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
</svg>
Apple Calendar
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleCalendarAction('download')}
className="cursor-pointer"
>
<Download className="h-4 w-4 mr-2" />
Download .ics File
</DropdownMenuItem>
{showSubscribe && <DropdownMenuSeparator />}
</>
)}
{showSubscribe && (
<>
{/* Subscription Options */}
<div className="px-2 py-1.5 text-sm font-semibold text-[#422268]">
Calendar Feeds
</div>
<DropdownMenuItem
onClick={() => handleCalendarAction('subscribe')}
className="cursor-pointer"
>
<RefreshCw className="h-4 w-4 mr-2" />
Subscribe to My Events
<div className="text-xs text-[#664fa3] mt-0.5">
Auto-syncs your RSVP'd events
</div>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleCalendarAction('all-events')}
className="cursor-pointer"
>
<Download className="h-4 w-4 mr-2" />
Download All Events
<div className="text-xs text-[#664fa3] mt-0.5">
One-time import of all upcoming events
</div>
</DropdownMenuItem>
</>
)}
{!event && !showSubscribe && (
<div className="px-2 py-6 text-center text-sm text-[#664fa3]">
No event selected
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -15,7 +15,12 @@ import {
ChevronLeft,
ChevronRight,
LogOut,
Menu
Menu,
Image,
FileText,
DollarSign,
Scale,
HardDrive
} from 'lucide-react';
const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
@@ -23,6 +28,9 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
const navigate = useNavigate();
const { user, logout } = useAuth();
const [pendingCount, setPendingCount] = useState(0);
const [storageUsed, setStorageUsed] = useState(0);
const [storageLimit, setStorageLimit] = useState(0);
const [storagePercentage, setStoragePercentage] = useState(0);
// Fetch pending approvals count
useEffect(() => {
@@ -44,6 +52,33 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
return () => clearInterval(interval);
}, []);
// Fetch storage usage
useEffect(() => {
const fetchStorageUsage = async () => {
try {
const response = await api.get('/admin/storage/usage');
setStorageUsed(response.data.total_bytes_used);
setStorageLimit(response.data.max_bytes_allowed);
setStoragePercentage(response.data.percentage);
} catch (error) {
console.error('Failed to fetch storage usage:', error);
}
};
fetchStorageUsage();
// Refresh storage usage every 60 seconds
const interval = setInterval(fetchStorageUsage, 60000);
return () => clearInterval(interval);
}, []);
const formatBytes = (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const handleLogout = () => {
logout();
navigate('/login');
@@ -85,7 +120,31 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
name: 'Events',
icon: Calendar,
path: '/admin/events',
disabled: true
disabled: false
},
{
name: 'Gallery',
icon: Image,
path: '/admin/gallery',
disabled: false
},
{
name: 'Newsletters',
icon: FileText,
path: '/admin/newsletters',
disabled: false
},
{
name: 'Financials',
icon: DollarSign,
path: '/admin/financials',
disabled: false
},
{
name: 'Bylaws',
icon: Scale,
path: '/admin/bylaws',
disabled: false
},
{
name: 'Roles',
@@ -233,6 +292,48 @@ const AdminSidebar = ({ isOpen, onToggle, isMobile }) => {
</div>
)}
{/* Storage Usage Widget */}
<div className="mb-2">
{isOpen ? (
<div className="px-4 py-3 bg-[#F8F7FB] rounded-lg">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-[#422268]">Storage Usage</span>
<span className="text-xs text-[#664fa3]">{storagePercentage}%</span>
</div>
<div className="w-full bg-[#ddd8eb] rounded-full h-2">
<div
className={`h-2 rounded-full transition-all ${
storagePercentage > 90 ? 'bg-red-500' :
storagePercentage > 75 ? 'bg-yellow-500' :
'bg-[#81B29A]'
}`}
style={{ width: `${storagePercentage}%` }}
/>
</div>
<p className="text-xs text-[#664fa3] mt-1">
{formatBytes(storageUsed)} / {formatBytes(storageLimit)}
</p>
</div>
) : (
<div className="flex justify-center">
<div className="relative group">
<HardDrive className={`h-5 w-5 ${
storagePercentage > 90 ? 'text-red-500' :
storagePercentage > 75 ? 'text-yellow-500' :
'text-[#664fa3]'
}`} />
{storagePercentage > 75 && (
<div className="absolute -top-1 -right-1 bg-red-500 h-2 w-2 rounded-full" />
)}
{/* Tooltip */}
<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">
Storage: {storagePercentage}%
</div>
</div>
</div>
)}
</div>
{/* Logout Button */}
<button
onClick={handleLogout}

View File

@@ -0,0 +1,122 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Calendar, Users, User, BookOpen, FileText, DollarSign, Scale } from 'lucide-react';
const MemberFooter = () => {
return (
<footer className="bg-[#422268] text-white mt-auto">
<div className="max-w-7xl mx-auto px-6 py-12">
<div className="grid md:grid-cols-4 gap-8">
{/* Logo & About */}
<div>
<h3 className="text-2xl font-bold mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
LOAF
</h3>
<p className="text-gray-300 text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Lesbian Organization of Atlanta Family
</p>
</div>
{/* Member Resources */}
<div>
<h4 className="font-semibold mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
Member Resources
</h4>
<ul className="space-y-2 text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<li>
<Link to="/members/calendar" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<Calendar className="h-4 w-4" />
Event Calendar
</Link>
</li>
<li>
<Link to="/members/directory" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<Users className="h-4 w-4" />
Members Directory
</Link>
</li>
<li>
<Link to="/members/profile" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<User className="h-4 w-4" />
My Profile
</Link>
</li>
<li>
<Link to="/events" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<BookOpen className="h-4 w-4" />
Events
</Link>
</li>
</ul>
</div>
{/* Documents */}
<div>
<h4 className="font-semibold mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
Documents
</h4>
<ul className="space-y-2 text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<li>
<Link to="/members/newsletters" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<FileText className="h-4 w-4" />
Newsletters
</Link>
</li>
<li>
<Link to="/members/financials" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<DollarSign className="h-4 w-4" />
Financial Reports
</Link>
</li>
<li>
<Link to="/members/bylaws" className="text-gray-300 hover:text-white flex items-center gap-2 transition-colors">
<Scale className="h-4 w-4" />
Bylaws
</Link>
</li>
</ul>
</div>
{/* Support */}
<div>
<h4 className="font-semibold mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
Support
</h4>
<ul className="space-y-2 text-sm" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<li>
<Link to="/profile" className="text-gray-300 hover:text-white transition-colors">
Account Settings
</Link>
</li>
<li>
<a href="/#contact" className="text-gray-300 hover:text-white transition-colors">
Contact Us
</a>
</li>
<li>
<a href="/#donate" className="text-gray-300 hover:text-white transition-colors">
Donate
</a>
</li>
</ul>
</div>
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-[#664fa3]">
<div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-gray-400" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<div className="flex gap-6">
<a href="/#terms" className="hover:text-white transition-colors">Terms of Service</a>
<a href="/#privacy" className="hover:text-white transition-colors">Privacy Policy</a>
</div>
<p>© 2025 LOAF. All rights reserved.</p>
</div>
</div>
</div>
</footer>
);
};
export default MemberFooter;

View File

@@ -2,93 +2,124 @@ import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { Button } from './ui/button';
import { Users, LogOut, LayoutDashboard } from 'lucide-react';
const Navbar = () => {
const { user, logout } = useAuth();
const navigate = useNavigate();
// LOAF logo (local)
const loafLogo = `${process.env.PUBLIC_URL}/loaf-logo.png`;
const handleLogout = () => {
logout();
navigate('/login');
};
return (
<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-[#ff9e77]" strokeWidth={1.5} />
<span className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>Membership</span>
<>
{/* Top Header - Member Actions */}
<header className="bg-gradient-to-r from-[#644c9f] to-[#48286e] px-16 py-4 flex justify-end items-center gap-6">
{user && (
<span className="text-white text-base font-medium" style={{ fontFamily: "'Poppins', sans-serif" }}>
Welcome, {user.first_name}
</span>
)}
{user?.role === 'admin' && (
<Link to="/admin">
<button
className="text-white text-base font-medium hover:opacity-80 transition-opacity bg-transparent border-none cursor-pointer"
style={{ fontFamily: "'Poppins', sans-serif" }}
data-testid="admin-nav-button"
>
Admin Panel
</button>
</Link>
<div className="flex items-center gap-4">
{user ? (
<>
{user.role === 'admin' && (
<Link to="/admin">
<Button
variant="ghost"
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
data-testid="admin-nav-button"
>
<LayoutDashboard className="h-4 w-4 mr-2" />
Admin
</Button>
</Link>
)}
<Link to="/events">
<Button
variant="ghost"
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
data-testid="events-nav-button"
>
Events
</Button>
</Link>
<Link to="/profile">
<Button
variant="ghost"
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
data-testid="profile-nav-button"
>
Profile
</Button>
</Link>
<Button
onClick={handleLogout}
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
data-testid="logout-button"
>
<LogOut className="h-4 w-4 mr-2" />
Logout
</Button>
</>
) : (
<>
<Link to="/login">
<Button
variant="ghost"
className="text-[#422268] hover:text-[#ff9e77] hover:bg-[#DDD8EB]/10"
data-testid="login-nav-button"
>
Login
</Button>
</Link>
<Link to="/register">
<Button
className="bg-[#DDD8EB] text-[#422268] hover:bg-white rounded-full px-6"
data-testid="register-nav-button"
>
Join Us
</Button>
</Link>
</>
)}
</div>
</div>
</div>
</nav>
)}
<button
onClick={handleLogout}
className="text-white text-base font-medium hover:opacity-80 transition-opacity bg-transparent border-none cursor-pointer"
style={{ fontFamily: "'Poppins', sans-serif" }}
data-testid="logout-button"
>
Logout
</button>
<Link to="/#donate">
<Button
className="bg-[#ff9e77] hover:bg-[#ff8c64] text-[#48286e] rounded-[25px] px-[54px] py-[10px] text-[16.5px] font-semibold h-[41px]"
style={{ fontFamily: "'Montserrat', sans-serif" }}
>
Donate
</Button>
</Link>
</header>
{/* Main Header - Member Navigation */}
<header className="bg-[#664fa3] px-16 py-2 flex justify-between items-center">
<Link to="/dashboard">
<img src={loafLogo} alt="LOAF Logo" className="h-28 w-28 object-contain" />
</Link>
<nav className="flex gap-10 items-center">
<Link
to="/"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Home
</Link>
<Link
to="/dashboard"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Dashboard
</Link>
<Link
to="/events"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
data-testid="events-nav-button"
>
Events
</Link>
<Link
to="/members/calendar"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Calendar
</Link>
<Link
to="/members/directory"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Directory
</Link>
<Link
to="/members/gallery"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Gallery
</Link>
<Link
to="/members/newsletters"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Documents
</Link>
<Link
to="/profile"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
data-testid="profile-nav-button"
>
Profile
</Link>
</nav>
</header>
</>
);
};

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Button } from './ui/button';
const PublicFooter = () => {
const loafLogo = `${process.env.PUBLIC_URL}/loaf-logo.png`;
return (
<>
{/* Main Footer */}
<footer className="bg-[#644c9f] px-16 py-0 flex items-center justify-center h-[420px]">
<div className="border-t border-[rgba(0,0,0,0.1)] py-20 flex gap-30 items-center justify-center flex-1">
<div className="w-[232px]">
<img src={loafLogo} alt="LOAF Logo" className="w-[232px] h-[232px] object-contain" />
</div>
<nav className="flex gap-28 items-start justify-center w-[811px]">
<div className="flex flex-col gap-2 w-[163px]">
<div className="pb-4">
<p className="text-white text-base font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>About</p>
</div>
<a href="/#history" className="text-[#ddd8eb] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>History</a>
<a href="/#mission" className="text-[#ddd8eb] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>Mission and Values</a>
<a href="/#board" className="text-[#ddd8eb] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>Board of Directors</a>
</div>
<div className="flex flex-col gap-2 w-[148px]">
<div className="pb-4">
<p className="text-white text-base font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>Connect</p>
</div>
<Link to="/become-a-member" className="text-[#ddd8eb] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>Become a Member</Link>
<a href="/#contact" className="text-[#ddd8eb] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>Contact Us</a>
<a href="/#resources" className="text-[#ddd8eb] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>Resources</a>
</div>
<div className="flex flex-col gap-2 items-center w-[271px]">
<div className="pb-4 w-full">
<Button className="bg-[#ff9e77] hover:bg-[#ff8c64] text-[#48286e] rounded-full px-6 py-3 text-lg font-medium w-[217px]">
Donate
</Button>
</div>
<p className="text-[#ddd8eb] text-base font-medium text-center w-full" style={{ fontFamily: "'Inter', sans-serif" }}>
LOAF is supported by<br />the Hollyfield Foundation
</p>
</div>
</nav>
</div>
</footer>
{/* Bottom Footer */}
<footer className="bg-gradient-to-r from-[#48286e] to-[#644c9f] border-t border-[rgba(0,0,0,0.1)] px-16 py-6 flex justify-between items-center h-[76px]">
<nav className="flex gap-8 items-center">
<a href="/#terms" className="text-[#c5b4e3] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>
Terms of Service
</a>
<a href="/#privacy" className="text-[#c5b4e3] text-base font-medium hover:text-white transition-colors" style={{ fontFamily: "'Inter', sans-serif" }}>
Privacy Policy
</a>
</nav>
<p className="text-[#c5b4e3] text-base font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
© 2025 LOAF. All Rights Reserved.
</p>
<p className="text-[#c5b4e3] text-base font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
Designed and Managed by{' '}
<a href="https://konceptkit.com/" className="text-[#d1c3e9] underline hover:text-white transition-colors">
Koncept Kit
</a>
</p>
</footer>
</>
);
};
export default PublicFooter;

View File

@@ -0,0 +1,108 @@
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Button } from './ui/button';
import { useAuth } from '../context/AuthContext';
const PublicNavbar = () => {
const { user, logout } = useAuth();
const navigate = useNavigate();
// LOAF logo (local)
const loafLogo = `${process.env.PUBLIC_URL}/loaf-logo.png`;
const handleAuthAction = () => {
if (user) {
logout();
navigate('/');
} else {
navigate('/login');
}
};
return (
<>
{/* Top Header - Auth Actions */}
<header className="bg-gradient-to-r from-[#644c9f] to-[#48286e] px-16 py-4 flex justify-end items-center gap-6">
<button
onClick={handleAuthAction}
className="text-white text-base font-medium hover:opacity-80 transition-opacity bg-transparent border-none cursor-pointer"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
{user ? 'Logout' : 'Login'}
</button>
{!user && (
<Link
to="/register"
className="text-white text-base font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Register
</Link>
)}
<Link to="/#donate">
<Button
className="bg-[#ff9e77] hover:bg-[#ff8c64] text-[#48286e] rounded-[25px] px-[54px] py-[10px] text-[16.5px] font-semibold h-[41px]"
style={{ fontFamily: "'Montserrat', sans-serif" }}
>
Donate
</Button>
</Link>
</header>
{/* Main Header - Navigation */}
<header className="bg-[#664fa3] px-16 py-2 flex justify-between items-center">
<Link to="/">
<img src={loafLogo} alt="LOAF Logo" className="h-28 w-28 object-contain" />
</Link>
<nav className="flex gap-10 items-center">
<Link
to="/#welcome"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Welcome
</Link>
<Link
to="/#about"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
About Us
</Link>
<Link
to={user ? "/dashboard" : "/become-a-member"}
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
{user ? 'Dashboard' : 'Become a Member'}
</Link>
{!user && (
<Link
to="/login"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Members Only
</Link>
)}
<Link
to="/#resources"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Resources
</Link>
<Link
to="/#contact"
className="text-white text-[17.5px] font-medium hover:opacity-80 transition-opacity"
style={{ fontFamily: "'Poppins', sans-serif" }}
>
Contact Us
</Link>
</nav>
</header>
</>
);
};
export default PublicNavbar;