Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev-dist/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ define(['./workbox-d70286d7'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812"
}, {
"url": "index.html",
"revision": "0.an3a855b9n4"
"revision": "0.otavhr08nak"
}], {});
workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
Expand Down
Binary file added public/notification.mp3
Binary file not shown.
4 changes: 2 additions & 2 deletions src/components/DealForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ export const DealForm: React.FC<DealFormProps> = ({
/>
{formData.investmentAmount && (
<div className="mt-2 p-2 bg-green-50 border border-green-200 rounded-md text-center">
<div className="text-xs text-gray-600 font-medium">After 5% Commission</div>
<div className="text-xs text-gray-600 font-medium">After 2% Commission</div>
<div className="text-sm font-bold text-green-700">
${(Number(formData.investmentAmount) * 0.95).toFixed(2)}
${(Number(formData.investmentAmount) * 0.98).toFixed(2)}
</div>
</div>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/DealPaymentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ export const DealPaymentModal: React.FC<DealPaymentModalProps> = ({
)}
{amount > 0 && (
<div className="mt-2 p-2 bg-green-50 border border-green-200 rounded text-center">
<div className="text-xs text-gray-600 font-medium">After 5% Commission</div>
<div className="text-xs text-gray-600 font-medium">After 2% Commission</div>
<div className="text-sm font-bold text-green-700">
${(Number(amount) * 0.95).toFixed(2)}
${(Number(amount) * 0.98).toFixed(2)}
</div>
</div>
)}
Expand Down
16 changes: 14 additions & 2 deletions src/components/common/NotificationDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,18 @@ export const NotificationDropdown: React.FC = () => {
} = useNotification();

const [isOpen, setIsOpen] = useState(false);
const [isPulsing, setIsPulsing] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

// Trigger pulse animation when unread count changes
useEffect(() => {
if (unreadCount > 0) {
setIsPulsing(true);
const timer = setTimeout(() => setIsPulsing(false), 2000);
return () => clearTimeout(timer);
}
}, [unreadCount]);

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
Expand All @@ -35,12 +45,14 @@ export const NotificationDropdown: React.FC = () => {
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="relative p-2 text-gray-600 hover:text-primary-600 hover:bg-gray-100 rounded-full transition-colors"
className={`relative p-2 text-gray-600 hover:text-primary-600 hover:bg-gray-100 rounded-full transition-colors ${
isPulsing ? 'animate-pulse' : ''
}`}
aria-label="Notifications"
>
<Bell size={20} />
{unreadCount > 0 && (
<span className="absolute top-1.5 right-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white border-2 border-white">
<span className="absolute top-1.5 right-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white border-2 border-white animate-bounce">
{unreadCount > 9 ? '9+' : unreadCount}
</span>
)}
Expand Down
15 changes: 14 additions & 1 deletion src/components/entrepreneur/EntrepreneurCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,20 @@ export const EntrepreneurCard: React.FC<EntrepreneurCardProps> = ({
<p className="text-sm text-gray-500 mb-2">{entrepreneur.startupName}</p>

<div className="flex flex-wrap gap-2 mb-3">
<Badge variant="primary" size="sm">{entrepreneur.industry}</Badge>
{(() => {
const inds = Array.isArray(entrepreneur.industry)
? (entrepreneur.industry as string[])
: (typeof entrepreneur.industry === 'string' && entrepreneur.industry.trim())
? [entrepreneur.industry as string]
: [];
return inds.length > 0 ? (
inds.map((ind, idx) => (
<Badge key={idx} variant="primary" size="sm">{ind}</Badge>
))
) : (
<Badge variant="primary" size="sm">--</Badge>
);
})()}
<Badge variant="gray" size="sm">{entrepreneur.teamSize}</Badge>
<Badge variant="accent" size="sm">Founded {entrepreneur.foundedYear}</Badge>
</div>
Expand Down
142 changes: 98 additions & 44 deletions src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import { useNavigate, Link, useLocation } from "react-router-dom";
import {
Menu,
Expand All @@ -14,6 +14,8 @@ import {
Settings,
ClipboardCheck,
Ban,
ChevronDown,
Home,
} from "lucide-react";
import { useAuth } from "../../context/AuthContext";
import { Avatar } from "../ui/Avatar";
Expand All @@ -22,17 +24,35 @@ import { NotificationDropdown } from "../common/NotificationDropdown";

export const Navbar: React.FC = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isProfileMenuOpen, setIsProfileMenuOpen] = useState(false);
const { user, logout } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const profileMenuRef = useRef<HTMLDivElement>(null);

const toggleMenu = () => setIsMenuOpen(!isMenuOpen);

// Auto-close menu on route change
useEffect(() => {
setIsMenuOpen(false);
setIsProfileMenuOpen(false);
}, [location.pathname]);

// Close profile menu when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
profileMenuRef.current &&
!profileMenuRef.current.contains(event.target as Node)
) {
setIsProfileMenuOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);

const handleLogout = () => {
logout();
navigate("/login");
Expand All @@ -50,43 +70,46 @@ export const Navbar: React.FC = () => {

// Determine profile route by role
const profileRoute = user ? `/profile/${user.role}/${user.userId}` : "/login";
const editProfileRoute = user
? `/settings`
: "/login";

// Navigation links by role
let navLinks: { icon: JSX.Element; text: string; path: string }[] = [];
let adminMobileLinks: { icon: JSX.Element; text: string; path: string }[] = [];
let navLinks: { icon: JSX.Element; label: string; path: string }[] = [];
let adminMobileLinks: { icon: JSX.Element; label: string; path: string }[] = [];

if (user?.role === "admin") {
navLinks = [
{
icon: <Shield size={18} />,
text: "Admin Dashboard",
label: "Dashboard",
path: dashboardRoute,
},
{
icon: <Briefcase size={18} />,
text: "Manage Users",
label: "Users",
path: "/admin/all-users",
},
{
icon: <CircleDollarSign size={18} />,
text: "Campaigns",
label: "Campaigns",
path: "/admin/campaigns",
},
{
icon: <UsersRoundIcon size={18} />,
text: "Supporters",
label: "Supporters",
path: "/admin/Supporters",
},
];
adminMobileLinks = [
{
icon: <ClipboardCheck size={18} />,
text: "Account Approvals",
label: "Approvals",
path: "/dashboard/admin/approvals",
},
{
icon: <Ban size={18} />,
text: "Suspended & Blocked",
label: "Suspended/Blocked",
path: "/admin/suspended-blocked",
},
];
Expand All @@ -95,20 +118,16 @@ export const Navbar: React.FC = () => {
navLinks = [
{
icon:
user?.role === "entrepreneur" ? (
<Building2 size={18} />
) : (
<CircleDollarSign size={18} />
),
text: "Dashboard",
<Home size={18} />,
label: "Dashboard",
path: dashboardRoute,
},
{
icon: <MessageCircle size={18} />,
text: "Messages",
label: "Messages",
path: "/messages",
},
{ icon: <User size={18} />, text: "Profile", path: profileRoute },
{ icon: <User size={18} />, label: "Profile", path: profileRoute },
];
}

Expand Down Expand Up @@ -136,39 +155,74 @@ export const Navbar: React.FC = () => {
{/* Desktop navigation */}
<div className="hidden md:flex md:items-center md:ml-6">
{user ? (
<div className="flex items-center space-x-4">
<div className="flex items-center space-x-3">
{navLinks.map((link, index) => (
<Link
key={index}
to={link.path}
className="inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 hover:text-primary-600 hover:bg-gray-50 rounded-md transition-colors duration-200"
title={link.label}
aria-label={link.label}
className="inline-flex items-center justify-center w-10 h-10 text-gray-700 hover:text-primary-600 hover:bg-gray-100 rounded-full transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200"
>
<span className="mr-2">{link.icon}</span>
{link.text}
{link.icon}
<span className="sr-only">{link.label}</span>
</Link>
))}

{/* Subtle divider between nav actions and profile cluster */}
<span className="hidden md:block h-6 w-px bg-gray-200" aria-hidden="true" />

{user && <NotificationDropdown />}
<Button
variant="ghost"
onClick={handleLogout}
leftIcon={<LogOut size={18} />}
>
Logout
</Button>
<Link
to={profileRoute}
className="flex items-center space-x-2 ml-2"
>
<Avatar
src={user.avatarUrl}
alt={user.name}
size="sm"
status={user.isOnline ? "online" : "offline"}
/>
<span className="text-sm font-medium text-gray-700">
{user.name}
</span>
</Link>

{/* Profile dropdown */}
<div className="relative" ref={profileMenuRef}>
<button
onClick={() => setIsProfileMenuOpen((prev) => !prev)}
className="inline-flex items-center gap-2 h-10 px-3 rounded-full border border-gray-200 bg-white hover:bg-gray-50 hover:border-primary-200 shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-200"
aria-label="Profile menu"
aria-haspopup="menu"
aria-expanded={isProfileMenuOpen}
>
<Avatar
src={user.avatarUrl}
alt={user.name}
size="sm"
status={user.isOnline ? "online" : "offline"}
/>
<span className="text-sm font-medium text-gray-800 whitespace-nowrap">
{user.name}
</span>
<ChevronDown
size={16}
className={`text-gray-500 transition-transform ${
isProfileMenuOpen ? "rotate-180" : "rotate-0"
}`}
/>
</button>

{isProfileMenuOpen && (
<div className="absolute right-0 mt-2 w-44 rounded-lg border border-gray-100 bg-white shadow-lg z-50 py-1">
<Link
to={editProfileRoute}
onClick={() => setIsProfileMenuOpen(false)}
className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
>
<User size={16} />
<span>Edit Profile</span>
</Link>
<button
onClick={() => {
handleLogout();
setIsProfileMenuOpen(false);
}}
className="flex w-full items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 text-left"
>
<LogOut size={16} />
<span>Logout</span>
</button>
</div>
)}
</div>
</div>
) : (
<div className="flex items-center space-x-4">
Expand Down Expand Up @@ -240,7 +294,7 @@ export const Navbar: React.FC = () => {
onClick={() => setIsMenuOpen(false)}
>
<span className="mr-3">{link.icon}</span>
{link.text}
{link.label}
</Link>
))}

Expand All @@ -253,7 +307,7 @@ export const Navbar: React.FC = () => {
onClick={() => setIsMenuOpen(false)}
>
<span className="mr-3">{link.icon}</span>
{link.text}
{link.label}
</Link>
))}

Expand Down
Loading