diff --git a/dev-dist/sw.js b/dev-dist/sw.js index c86c2a032..1df8fbe9e 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -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"), { diff --git a/public/notification.mp3 b/public/notification.mp3 new file mode 100644 index 000000000..0430b396b Binary files /dev/null and b/public/notification.mp3 differ diff --git a/src/components/DealForm.tsx b/src/components/DealForm.tsx index 9aecd9d3b..d4f53c60f 100644 --- a/src/components/DealForm.tsx +++ b/src/components/DealForm.tsx @@ -184,9 +184,9 @@ export const DealForm: React.FC = ({ /> {formData.investmentAmount && (
-
After 5% Commission
+
After 2% Commission
- ${(Number(formData.investmentAmount) * 0.95).toFixed(2)} + ${(Number(formData.investmentAmount) * 0.98).toFixed(2)}
)} diff --git a/src/components/DealPaymentModal.tsx b/src/components/DealPaymentModal.tsx index 43b93c4b5..3472b1985 100644 --- a/src/components/DealPaymentModal.tsx +++ b/src/components/DealPaymentModal.tsx @@ -143,9 +143,9 @@ export const DealPaymentModal: React.FC = ({ )} {amount > 0 && (
-
After 5% Commission
+
After 2% Commission
- ${(Number(amount) * 0.95).toFixed(2)} + ${(Number(amount) * 0.98).toFixed(2)}
)} diff --git a/src/components/common/NotificationDropdown.tsx b/src/components/common/NotificationDropdown.tsx index 4dbde12d4..845a8ccc3 100644 --- a/src/components/common/NotificationDropdown.tsx +++ b/src/components/common/NotificationDropdown.tsx @@ -17,8 +17,18 @@ export const NotificationDropdown: React.FC = () => { } = useNotification(); const [isOpen, setIsOpen] = useState(false); + const [isPulsing, setIsPulsing] = useState(false); const dropdownRef = useRef(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)) { @@ -35,12 +45,14 @@ export const NotificationDropdown: React.FC = () => {
- - - - {user.name} - - + + {/* Profile dropdown */} +
+ + + {isProfileMenuOpen && ( +
+ setIsProfileMenuOpen(false)} + className="flex items-center gap-2 px-3 py-2 text-sm text-gray-700 hover:bg-gray-50" + > + + Edit Profile + + +
+ )} +
) : (
@@ -240,7 +294,7 @@ export const Navbar: React.FC = () => { onClick={() => setIsMenuOpen(false)} > {link.icon} - {link.text} + {link.label} ))} @@ -253,7 +307,7 @@ export const Navbar: React.FC = () => { onClick={() => setIsMenuOpen(false)} > {link.icon} - {link.text} + {link.label} ))} diff --git a/src/components/settings/EntrepreneurSettings.tsx b/src/components/settings/EntrepreneurSettings.tsx index 78146dec7..e6d256cba 100644 --- a/src/components/settings/EntrepreneurSettings.tsx +++ b/src/components/settings/EntrepreneurSettings.tsx @@ -10,6 +10,9 @@ import { Entrepreneur, UserRole } from "../../types"; import { useAuth } from "../../context/AuthContext"; import { useLocation, useNavigate } from "react-router-dom"; import { Card, CardHeader } from "../ui/Card"; +import { X, Plus, ChevronDown, Check, Loader2 } from "lucide-react"; +import axios from "axios"; +import toast from "react-hot-toast"; type User = { name: string; @@ -24,6 +27,10 @@ export const EntrepreneurSettings: React.FC = () => { const navigate = useNavigate(); const checkIfSettingPage = location.pathname === "/settings" ? true : false; const [entrepreneur, setEnterpreneur] = useState(); + const [industries, setIndustries] = useState<{ _id: string; name: string; isCustom: boolean; }[]>([]); + const [showIndustryDropdown, setShowIndustryDropdown] = useState(false); + const [customIndustry, setCustomIndustry] = useState(""); + const [isAddingCustom, setIsAddingCustom] = useState(false); useEffect(() => { const fetchEntrepreneur = async () => { @@ -35,6 +42,19 @@ export const EntrepreneurSettings: React.FC = () => { fetchEntrepreneur(); }, [user]); + useEffect(() => { + const fetchIndustries = async () => { + try { + const response = await axios.get(`${import.meta.env.VITE_BACKEND_URL}/industry/get-all`); + setIndustries(response.data); + } catch (error) { + console.error("Failed to fetch industries:", error); + toast.error("Failed to load industries"); + } + }; + fetchIndustries(); + }, []); + const initialData = useMemo( () => ({ userId: entrepreneur?.userId, @@ -170,12 +190,190 @@ export const EntrepreneurSettings: React.FC = () => { onChange={handleUserChange} /> - +
+ +
+ {/* Selected Industries */} +
+ {(formData.industry && formData.industry.length > 0) ? formData.industry.map((ind, idx) => ( + + {ind} + + + )) : ( + No industries selected + )} +
+ + {/* Dropdown Button */} + + + {/* Dropdown Menu */} + {showIndustryDropdown && ( +
+ {/* Custom Industry Input */} +
+
+ setCustomIndustry(e.target.value)} + placeholder="Add custom industry..." + className="flex-1 px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-sm" + onKeyPress={async (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + const trimmed = customIndustry.trim(); + if (!trimmed) return; + + const existsInList = industries.some(ind => ind.name.toLowerCase() === trimmed.toLowerCase()); + const alreadySelected = formData.industry?.some(ind => ind.toLowerCase() === trimmed.toLowerCase()); + + if (alreadySelected) { + toast.error('Industry already selected'); + return; + } + + if (!existsInList) { + try { + setIsAddingCustom(true); + const response = await axios.post(`${import.meta.env.VITE_BACKEND_URL}/industry/add-custom`, { + name: trimmed, + userId: user?.userId + }); + setIndustries(prev => [...prev, response.data]); + toast.success('Custom industry added'); + } catch (error: any) { + if (error.response?.data?.message === 'Industry already exists') { + toast.error('Industry already exists in database'); + } else { + toast.error('Failed to add custom industry'); + } + console.error(error); + } finally { + setIsAddingCustom(false); + } + } + + const currentIndustries = formData.industry || []; + setFormData(prev => ({ + ...prev, + industry: [...currentIndustries, trimmed] + })); + setCustomIndustry(''); + } + }} + /> + +
+
+ + {/* Industry Options */} +
+ {industries.map((industry) => { + const isSelected = formData.industry?.includes(industry.name); + return ( + + ); + })} +
+
+ )} +
+
{ const [entrepreneur, setEnterpreneur] = useState(); const isEditMode = checkIfSettingPage && entrepreneur; const [isSubmitting, setIsSubmitting] = useState(false); + const [industries, setIndustries] = useState<{ _id: string; name: string; isCustom: boolean; }[]>([]); + const [showIndustryDropdown, setShowIndustryDropdown] = useState(false); + const [customIndustry, setCustomIndustry] = useState(""); + const [isAddingCustom, setIsAddingCustom] = useState(false); useEffect(() => { const fetchEntrepreneur = async () => { @@ -52,6 +60,19 @@ export const EntrepreneurSetup: React.FC = () => { fetchEntrepreneur(); }, [user]); + useEffect(() => { + const fetchIndustries = async () => { + try { + const response = await axios.get(`${import.meta.env.VITE_BACKEND_URL}/industry/get-all`); + setIndustries(response.data); + } catch (error) { + console.error("Failed to fetch industries:", error); + toast.error("Failed to load industries"); + } + }; + fetchIndustries(); + }, []); + const initialData = useMemo( () => ({ userId: entrepreneur?.userId, @@ -241,21 +262,194 @@ export const EntrepreneurSetup: React.FC = () => {
-
+
-
- -
+
+ {/* Selected Industries */} +
+ {(formData.industry && formData.industry.length > 0) ? formData.industry.map((ind, idx) => ( + + {ind} + + + )) : ( + No industries selected + )} +
+ + {/* Dropdown Button */} + + + {/* Dropdown Menu */} + {showIndustryDropdown && ( +
+ {/* Custom Industry Input */} +
+
+ setCustomIndustry(e.target.value)} + placeholder="Add custom industry..." + className="flex-1 px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent text-sm" + onKeyPress={async (e) => { + if (e.key === 'Enter' && customIndustry.trim()) { + e.preventDefault(); + const trimmed = customIndustry.trim(); + // Check if already exists in dropdown + const existsInList = industries.some(ind => ind.name.toLowerCase() === trimmed.toLowerCase()); + // Check if already selected + const alreadySelected = formData.industry?.some(ind => ind.toLowerCase() === trimmed.toLowerCase()); + + if (alreadySelected) { + toast.error('Industry already selected'); + return; + } + + if (!existsInList) { + // Add to database + try { + setIsAddingCustom(true); + const response = await axios.post(`${import.meta.env.VITE_BACKEND_URL}/industry/add-custom`, { + name: trimmed, + userId: user?.userId + }); + // Add to local industries list + setIndustries(prev => [...prev, response.data]); + toast.success('Custom industry added'); + } catch (error: any) { + if (error.response?.data?.message === 'Industry already exists') { + toast.error('Industry already exists in database'); + } else { + toast.error('Failed to add custom industry'); + } + console.error(error); + } finally { + setIsAddingCustom(false); + } + } + + // Add to selected + const currentIndustries = formData.industry || []; + setFormData(prev => ({ + ...prev, + industry: [...currentIndustries, trimmed] + })); + setCustomIndustry(''); + } + }} + /> + +
+
+ + {/* Industry Options */} +
+ {industries.map((industry) => { + const isSelected = formData.industry?.includes(industry.name); + return ( + + ); + })} +
+
+ )}
diff --git a/src/components/settings/InvestorSettings.tsx b/src/components/settings/InvestorSettings.tsx index 9129a91dc..aea33f042 100644 --- a/src/components/settings/InvestorSettings.tsx +++ b/src/components/settings/InvestorSettings.tsx @@ -9,9 +9,11 @@ import { } from "../../data/users"; import { Button } from "../ui/Button"; import { Input } from "../ui/Input"; -import { Eraser } from "lucide-react"; +import { Eraser, ChevronDown, X, Check, Search } from "lucide-react"; import { useLocation, useNavigate } from "react-router-dom"; import { Card, CardHeader } from "../ui/Card"; +import axios from "axios"; +import toast from "react-hot-toast"; type User = { name: string; @@ -27,6 +29,9 @@ export const InvestorSettings: React.FC = () => { const { user, register } = useAuth(); const [investor, setInvestor] = useState(); + const [industries, setIndustries] = useState<{ _id: string; name: string; isCustom: boolean }[]>([]); + const [showIndustryDropdown, setShowIndustryDropdown] = useState(false); + const [industryQuery, setIndustryQuery] = useState(""); const initialInvestorData = useMemo( () => ({ userId: investor?.userId || user?.userId, @@ -56,6 +61,20 @@ export const InvestorSettings: React.FC = () => { fetchInvestors(); }, [user]); + // Fetch industries from backend + useEffect(() => { + const fetchIndustries = async () => { + try { + const res = await axios.get(`${import.meta.env.VITE_BACKEND_URL}/industry/get-all`); + setIndustries(res.data || []); + } catch (error) { + console.error("Failed to load industries", error); + toast.error("Failed to load industries"); + } + }; + fetchIndustries(); + }, []); + useEffect(() => { setInvestorFormData(initialInvestorData); }, [initialInvestorData]); @@ -167,50 +186,104 @@ export const InvestorSettings: React.FC = () => { onSubmit={handleInvestorSubmit} className="gap-5 flex flex-col text-sm justify-center mt-5" > -
- -