- {entrepreneur.name}
+ {entrepreneur.userId.name}
@@ -142,7 +198,7 @@ export const EntrepreneurProfile: React.FC = () => {
{entrepreneur.industry}
- {entrepreneur.location}
+ {entrepreneur.userId.location}
@@ -207,7 +263,7 @@ export const EntrepreneurProfile: React.FC = () => {
About
- {entrepreneur.bio}
+ {entrepreneur.userId.bio}
diff --git a/src/pages/profile/InvestorProfile.tsx b/src/pages/profile/InvestorProfile.tsx
index 3ac670162..3cca86981 100644
--- a/src/pages/profile/InvestorProfile.tsx
+++ b/src/pages/profile/InvestorProfile.tsx
@@ -1,43 +1,54 @@
-import React, { useEffect } from 'react';
-import { useParams, Link } from 'react-router-dom';
-import { MessageCircle, Building2, MapPin, UserCircle, BarChart3, Briefcase } from 'lucide-react';
-import { Avatar } from '../../components/ui/Avatar';
-import { Button } from '../../components/ui/Button';
-import { Card, CardBody, CardHeader } from '../../components/ui/Card';
-import { Badge } from '../../components/ui/Badge';
-import { useAuth } from '../../context/AuthContext';
-
-import { getInvestorById } from '../../data/users';
-import { Investor } from '../../types';
+import React, { useEffect, useState } from "react";
+import { useParams, Link } from "react-router-dom";
+import {
+ MessageCircle,
+ Building2,
+ MapPin,
+ UserCircle,
+ BarChart3,
+ Briefcase,
+} from "lucide-react";
+import { Avatar } from "../../components/ui/Avatar";
+import { Button } from "../../components/ui/Button";
+import { Card, CardBody, CardHeader } from "../../components/ui/Card";
+import { Badge } from "../../components/ui/Badge";
+import { useAuth } from "../../context/AuthContext";
+import { getInvestorById } from "../../data/users";
+import { Investor } from "../../types";
export const InvestorProfile: React.FC = () => {
const { id } = useParams<{ id: string }>();
const { user: currentUser } = useAuth();
- let investor :Investor;
+ const [investor, setInvestor] = useState();
// Fetch investor data
- useEffect(()=>{
- const fetchInvestors = async() =>{
- investor = await getInvestorById(id);
- }
- fetchInvestors();
- },[]);
-
-
-
- if (!investor || investor.role !== 'investor') {
+ useEffect(() => {
+ const fetchInvestors = async () => {
+ const investor = await getInvestorById(id);
+ setInvestor(investor);
+ };
+ fetchInvestors();
+ }, []);
+
+ if (!currentUser) return null;
+ if (!investor || investor.role !== "investor") {
return (
Investor not found
-
The investor profile you're looking for doesn't exist or has been removed.
+
+ The investor profile you're looking for doesn't exist or has been
+ removed.
+
-
Back to Dashboard
+
+ Back to Dashboard
+
);
}
-
- const isCurrentUser = currentUser?.id === investor.id;
-
+
+ const isCurrentUser = currentUser?.userId === investor.userId;
+
return (
{/* Profile header */}
@@ -48,52 +59,50 @@ export const InvestorProfile: React.FC = () => {
src={investor.avatarUrl}
alt={investor.name}
size="xl"
- status={investor.isOnline ? 'online' : 'offline'}
+ status={investor.isOnline ? "online" : "offline"}
className="mx-auto sm:mx-0"
/>
-
+
-
{investor.name}
+
+ {investor.name}
+
Investor • {investor.totalInvestments} investments
-
+
San Francisco, CA
- {investor.investmentStage.map((stage, index) => (
- {stage}
- ))}
+ {investor.investmentStage &&
+ investor.investmentStage.map((stage, index) => (
+
+ {stage}
+
+ ))}
-
+
{!isCurrentUser && (
-
- }
- >
- Message
-
+
+ }>Message
)}
-
+
{isCurrentUser && (
- }
- >
+ }>
Edit Profile
)}
-
+
{/* Main content - left side */}
@@ -106,34 +115,48 @@ export const InvestorProfile: React.FC = () => {
{investor.bio}
-
+
{/* Investment Interests */}
- Investment Interests
+
+ Investment Interests
+
-
Industries
+
+ Industries
+
- {investor.investmentInterests.map((interest, index) => (
- {interest}
- ))}
+ {investor.investmentInterests &&
+ investor.investmentInterests.map((interest, index) => (
+
+ {interest}
+
+ ))}
-
+
-
Investment Stages
+
+ Investment Stages
+
- {investor.investmentStage.map((stage, index) => (
- {stage}
- ))}
+ {investor.investmentStage&&
+ investor.investmentStage.map((stage, index) => (
+
+ {stage}
+
+ ))}
-
+
-
Investment Criteria
+
+ Investment Criteria
+
@@ -156,76 +179,112 @@ export const InvestorProfile: React.FC = () => {
-
+
{/* Portfolio Companies */}
- Portfolio Companies
- {investor.portfolioCompanies.length} companies
+
+ Portfolio Companies
+
+
+ {investor.portfolioCompanies &&
+ investor.portfolioCompanies.length}{" "}
+ companies
+
- {investor.portfolioCompanies.map((company, index) => (
-
-
-
-
-
-
{company}
-
Invested in 2022
+ {investor.portfolioCompanies &&
+ investor.portfolioCompanies.map((company, index) => (
+
+
+
+
+
+
+ {company}
+
+
+ Invested in 2022
+
+
-
- ))}
+ ))}
-
+
{/* Sidebar - right side */}
{/* Investment Details */}
- Investment Details
+
+ Investment Details
+
-
Investment Range
+
+ Investment Range
+
- {investor.minimumInvestment} - {investor.maximumInvestment}
+ {investor.minimumInvestment && investor.minimumInvestment} -{" "}
+ {investor.maximumInvestment && investor.maximumInvestment}
-
+
-
Total Investments
-
{investor.totalInvestments} companies
+
+ Total Investments
+
+
+ {investor.totalInvestments} companies
+
-
+
-
Typical Investment Timeline
+
+ Typical Investment Timeline
+
3-5 years
-
+
-
Investment Focus
+
+ Investment Focus
+
@@ -233,39 +292,53 @@ export const InvestorProfile: React.FC = () => {
-
+
{/* Stats */}
- Investment Stats
+
+ Investment Stats
+
-
Successful Exits
-
4
+
+ Successful Exits
+
+
+ 4
+
-
+
-
Avg. ROI
-
3.2x
+
+ Avg. ROI
+
+
+ 3.2x
+
-
+
-
Active Investments
-
{investor.portfolioCompanies.length}
+
+ Active Investments
+
+
+ {investor.portfolioCompanies && investor.portfolioCompanies.length}
+
@@ -277,4 +350,4 @@ export const InvestorProfile: React.FC = () => {
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index e1a032c48..d0391131f 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -9,18 +9,19 @@ import { useAuth } from "../../context/AuthContext";
import { Navigate } from "react-router-dom";
export const SettingsPage: React.FC = () => {
- const { user,updateProfile,userData } = useAuth();
+ const { user, updateProfile, userData } = useAuth();
if (!user || !userData) return null;
- console.log(userData)
- const [userDetails, setUserDetails] = useState({
+
+ const initialValues = {
name: userData.name,
email: userData.email,
role: userData.role,
bio: userData.bio || "",
location: userData.location || "",
avatarUrl: userData.avatarUrl || "",
- });
+ };
+ const [userDetails, setUserDetails] = useState(initialValues);
const [isFileUploaded, setIsFileUploaded] = useState(false);
const handleChange = (e: Event) => {
const { name, value, files } = e.target;
@@ -31,10 +32,14 @@ export const SettingsPage: React.FC = () => {
setUserDetails({ ...userDetails, [name]: value });
}
};
- const handleSubmit = async (e:Event) => {
+ const handleSubmit = async (e: Event) => {
e.preventDefault();
-
- updateProfile(userData.userId,userDetails);
+
+ updateProfile(userData.userId, userDetails);
+ };
+ const handleCancel = (e) => {
+ e.preventDefault();
+ setUserDetails(initialValues);
};
return (
@@ -50,9 +55,12 @@ export const SettingsPage: React.FC = () => {
- {
-
- }}>
+ {
+ ;
+ }}
+ >
Profile
@@ -124,26 +132,19 @@ export const SettingsPage: React.FC = () => {
+
-
-
@@ -155,12 +156,14 @@ export const SettingsPage: React.FC = () => {
rows={4}
name="bio"
onChange={handleChange}
- defaultValue={userDetails.bio}
+ value={userDetails.bio}
>
- Cancel
+
+ Cancel
+
Save Changes
diff --git a/src/types/index.ts b/src/types/index.ts
index a1c910957..355b88cf1 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -12,8 +12,6 @@ export interface User {
}
export interface Entrepreneur extends User {
- userId:string;
- role: 'entrepreneur';
startupName: string;
pitchSummary: string;
fundingNeeded: string;
From 1c645cc1018cc21ac3b74f03e200cccb5f88fa1f Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Fri, 29 Aug 2025 23:13:28 +0500
Subject: [PATCH 05/49] profile updation
---
.../entrepreneur/EntrepreneurCard.tsx | 4 +-
src/components/ui/Badge.tsx | 3 +
src/data/users.ts | 96 ++---
src/pages/dashboard/InvestorDashboard.tsx | 14 +-
src/pages/entrepreneurs/EntrepreneursPage.tsx | 20 +-
src/pages/investors/InvestorsPage.tsx | 138 ++++---
src/pages/messages/MessagesPage.tsx | 2 +-
src/pages/profile/EntrepreneurProfile.tsx | 182 +++++---
src/pages/profile/InvestorProfile.tsx | 389 +++++++++++++++---
src/types/index.ts | 50 ++-
10 files changed, 628 insertions(+), 270 deletions(-)
diff --git a/src/components/entrepreneur/EntrepreneurCard.tsx b/src/components/entrepreneur/EntrepreneurCard.tsx
index a2cdaeee7..bd284146c 100644
--- a/src/components/entrepreneur/EntrepreneurCard.tsx
+++ b/src/components/entrepreneur/EntrepreneurCard.tsx
@@ -19,12 +19,12 @@ export const EntrepreneurCard: React.FC = ({
const navigate = useNavigate();
const handleViewProfile = () => {
- navigate(`/profile/entrepreneur/${entrepreneur._id}`);
+ navigate(`/profile/entrepreneur/${entrepreneur.userId ||entrepreneur._id}`);
};
const handleMessage = (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent card click
- navigate(`/chat/${entrepreneur._id}`);
+ navigate(`/chat/${entrepreneur.userId ||entrepreneur._id}`);
};
return (
diff --git a/src/components/ui/Badge.tsx b/src/components/ui/Badge.tsx
index 8c84dc3ed..ee33fa675 100644
--- a/src/components/ui/Badge.tsx
+++ b/src/components/ui/Badge.tsx
@@ -9,6 +9,7 @@ interface BadgeProps {
size?: BadgeSize;
rounded?: boolean;
className?: string;
+ onClick?:EventListener;
}
export const Badge: React.FC = ({
@@ -17,6 +18,7 @@ export const Badge: React.FC = ({
size = 'md',
rounded = false,
className = '',
+ onClick={},
}) => {
const variantClasses = {
primary: 'bg-primary-100 text-primary-800',
@@ -39,6 +41,7 @@ export const Badge: React.FC = ({
return (
{children}
diff --git a/src/data/users.ts b/src/data/users.ts
index d55e02f78..04e1b4556 100644
--- a/src/data/users.ts
+++ b/src/data/users.ts
@@ -8,8 +8,8 @@ export const getInvestorsFromDb = async () => {
const res = await axios.get(URL + "/investor/get-investors", {
withCredentials: true,
});
- const { users } = res.data;
- return users;
+ const { investors } = res.data;
+ return investors;
} catch (err) {
console.log(err);
}
@@ -20,9 +20,8 @@ export const getInvestorById = async (id) => {
const res = await axios.get(URL + "/investor/get-investor-by-id/" + id, {
withCredentials: true,
});
- const { user } = res.data;
- const filteredUser = filterInvestor(user);
- return filteredUser;
+ const { investor } = res.data;
+ return investor;
} catch (err) {
console.log(err);
}
@@ -33,8 +32,8 @@ export const getEnterprenuerFromDb = async () => {
const res = await axios.get(URL + "/entrepreneur/get-entrepreneurs", {
withCredentials: true,
});
- const { users } = res.data;
- return users;
+ const { entrepreneurs } = res.data;
+ return entrepreneurs;
} catch (err) {
console.log(err);
}
@@ -48,14 +47,15 @@ export const getEnterpreneurById = async (id) => {
withCredentials: true,
}
);
- const { entrepreneur } = res.data;
+ const {entrepreneur} = res.data;
+ console.log(entrepreneur);
return entrepreneur;
} catch (err) {
console.log(err);
}
};
-export const updateEntrepreneurData = async (formData: Entrepreneur) => {
+export const updateEntrepreneurData = async (formData: any) => {
try {
await axios.put(
`${URL}/entrepreneur/update-profile/${formData.userId}`,
@@ -69,68 +69,18 @@ export const updateEntrepreneurData = async (formData: Entrepreneur) => {
console.log(err);
}
};
-function filterInvestor(obj: Investor): Investor {
- const {
- userId,
- name,
- bio,
- role,
- location,
- email,
- avatarUrl,
- investmentInterests,
- investmentStage,
- portfolioCompanies,
- totalInvestments,
- minimumInvestment,
- maximumInvestment,
- } = obj;
- return {
- userId,
- name,
- bio,
- role,
- email,
- location,
- avatarUrl,
- investmentInterests,
- investmentStage,
- portfolioCompanies,
- totalInvestments,
- minimumInvestment,
- maximumInvestment,
- };
-}
-function filterEntrepreneur(obj: Entrepreneur): Entrepreneur {
- const {
- userId,
- name,
- bio,
- role,
- startupName,
- pitchSummary,
- fundingNeeded,
- industry,
- teamSize,
- location,
- foundedYear,
- email,
- avatarUrl,
- } = obj;
- return {
- userId,
- name,
- bio,
- role,
- foundedYear,
- email,
- startupName,
- pitchSummary,
- fundingNeeded,
- industry,
- teamSize,
- location,
- avatarUrl,
- };
-}
+export const updateInvestorData = async (formData: any) => {
+ try {
+ await axios.put(
+ `${URL}/investor/update-profile/${formData.userId}`,
+ formData,
+ {
+ withCredentials: true,
+ }
+ );
+ toast.success("User data updated successfully.");
+ } catch (err) {
+ console.log(err);
+ }
+};
diff --git a/src/pages/dashboard/InvestorDashboard.tsx b/src/pages/dashboard/InvestorDashboard.tsx
index c8d9a2e80..cb96e5989 100644
--- a/src/pages/dashboard/InvestorDashboard.tsx
+++ b/src/pages/dashboard/InvestorDashboard.tsx
@@ -20,13 +20,18 @@ export const InvestorDashboard: React.FC = () => {
// Get collaboration requests sent by this investor
const sentRequests = getRequestsFromInvestor(user.id);
// const requestedEntrepreneurIds = sentRequests.map(req => req.entrepreneurId);
- const [entrepreneurs,setEnterprenuers] = useState([{}]);
+ const [entrepreneurs,setEnterprenuers] = useState([]);
+ const industries = [];
useEffect(() => {
const fetchData = async()=>{
if (user) {
const entrepreneurs = await getEnterprenuerFromDb();
setEnterprenuers(entrepreneurs);
+ entrepreneurs.map(e=>{
+ industries.push(e.industry);
+ });
+ console.log(industries);
}
}
fetchData();
@@ -49,7 +54,6 @@ export const InvestorDashboard: React.FC = () => {
// });
// Get unique industries for filter
- const industries = [];
// Toggle industry selection
const toggleIndustry = (industry: string) => {
@@ -120,7 +124,7 @@ export const InvestorDashboard: React.FC = () => {
Total Startups
-
{entrepreneurs.length}
+
{entrepreneurs && entrepreneurs.length}
@@ -165,10 +169,10 @@ export const InvestorDashboard: React.FC = () => {
- {entrepreneurs.length > 0 ? (
+ {entrepreneurs && entrepreneurs.length > 0 ? (
- {entrepreneurs.map(entrepreneur => (
+ {entrepreneurs && entrepreneurs.map(entrepreneur => (
diff --git a/src/pages/entrepreneurs/EntrepreneursPage.tsx b/src/pages/entrepreneurs/EntrepreneursPage.tsx
index 1f1c097af..60037acc0 100644
--- a/src/pages/entrepreneurs/EntrepreneursPage.tsx
+++ b/src/pages/entrepreneurs/EntrepreneursPage.tsx
@@ -1,16 +1,30 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { Search, Filter, MapPin } from 'lucide-react';
import { Input } from '../../components/ui/Input';
import { Card, CardHeader, CardBody } from '../../components/ui/Card';
import { Badge } from '../../components/ui/Badge';
import { EntrepreneurCard } from '../../components/entrepreneur/EntrepreneurCard';
+import { useAuth } from '../../context/AuthContext';
+import { getEnterprenuerFromDb } from '../../data/users';
+import { Entrepreneur } from '../../types';
export const EntrepreneursPage: React.FC = () => {
const [searchQuery, setSearchQuery] = useState('');
const [selectedIndustries, setSelectedIndustries] = useState
([]);
const [selectedFundingRange, setSelectedFundingRange] = useState([]);
-
+ const {user} =useAuth();
// Get unique industries and funding ranges
+ const [entrepreneurs,setEnterprenuers] = useState([]);
+
+ useEffect(() => {
+ const fetchData = async()=>{
+ if (user) {
+ const entrepreneurs = await getEnterprenuerFromDb();
+ setEnterprenuers(entrepreneurs);
+ }
+ }
+ fetchData();
+ }, []);
const allIndustries = Array.from(new Set(entrepreneurs.map(e => e.industry)));
const fundingRanges = ['< $500K', '$500K - $1M', '$1M - $5M', '> $5M'];
@@ -153,7 +167,7 @@ export const EntrepreneursPage: React.FC = () => {
{filteredEntrepreneurs.map(entrepreneur => (
))}
diff --git a/src/pages/investors/InvestorsPage.tsx b/src/pages/investors/InvestorsPage.tsx
index 35a77a05d..230a7e2cc 100644
--- a/src/pages/investors/InvestorsPage.tsx
+++ b/src/pages/investors/InvestorsPage.tsx
@@ -1,60 +1,89 @@
-import React, { useState } from 'react';
-import { Search, Filter, MapPin } from 'lucide-react';
-import { Input } from '../../components/ui/Input';
-import { Card, CardHeader, CardBody } from '../../components/ui/Card';
-import { Badge } from '../../components/ui/Badge';
-import { InvestorCard } from '../../components/investor/InvestorCard';
+import React, { useEffect, useState } from "react";
+import { Search, Filter, MapPin } from "lucide-react";
+import { Input } from "../../components/ui/Input";
+import { Card, CardHeader, CardBody } from "../../components/ui/Card";
+import { Badge } from "../../components/ui/Badge";
+import { InvestorCard } from "../../components/investor/InvestorCard";
+import { useAuth } from "../../context/AuthContext";
+import { getInvestorsFromDb } from "../../data/users";
+import { getRequestsForEntrepreneur } from "../../data/collaborationRequests";
+import { Investor } from "../../types";
export const InvestorsPage: React.FC = () => {
- const [searchQuery, setSearchQuery] = useState('');
+ const [searchQuery, setSearchQuery] = useState("");
const [selectedStages, setSelectedStages] = useState
([]);
const [selectedInterests, setSelectedInterests] = useState([]);
-
+
+ const [investors, setInvestors] = useState([]);
+ const { user } = useAuth();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ if (user) {
+ const investors = await getInvestorsFromDb();
+ console.log(investors);
+ setInvestors(investors ? investors : []);
+
+ const requests = getRequestsForEntrepreneur(user.id);
+ }
+ };
+ fetchData();
+ }, []);
+ if (!user) return null;
// Get unique investment stages and interests
- const allStages = Array.from(new Set(investors.flatMap(i => i.investmentStage)));
- const allInterests = Array.from(new Set(investors.flatMap(i => i.investmentInterests)));
-
+ const allStages = Array.from(
+ new Set(investors.flatMap((i) => i.investmentStage || ""))
+ );
+ const allInterests = Array.from(
+ new Set(investors.flatMap((i) => i.investmentInterests || ""))
+ );
+
// Filter investors based on search and filters
- const filteredInvestors = investors.filter(investor => {
- const matchesSearch = searchQuery === '' ||
+ const filteredInvestors = investors.filter((investor) => {
+ const matchesSearch =
+ searchQuery === "" ||
investor.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
investor.bio.toLowerCase().includes(searchQuery.toLowerCase()) ||
- investor.investmentInterests.some(interest =>
+ investor.investmentInterests?.some((interest) =>
interest.toLowerCase().includes(searchQuery.toLowerCase())
);
-
- const matchesStages = selectedStages.length === 0 ||
- investor.investmentStage.some(stage => selectedStages.includes(stage));
-
- const matchesInterests = selectedInterests.length === 0 ||
- investor.investmentInterests.some(interest => selectedInterests.includes(interest));
-
+
+ const matchesStages =
+ selectedStages.length === 0 ||
+ investor.investmentStage?.some((stage) => selectedStages.includes(stage));
+
+ const matchesInterests =
+ selectedInterests.length === 0 ||
+ investor.investmentInterests?.some((interest) =>
+ selectedInterests.includes(interest)
+ );
+
return matchesSearch && matchesStages && matchesInterests;
});
-
+
const toggleStage = (stage: string) => {
- setSelectedStages(prev =>
- prev.includes(stage)
- ? prev.filter(s => s !== stage)
- : [...prev, stage]
+ setSelectedStages((prev) =>
+ prev.includes(stage) ? prev.filter((s) => s !== stage) : [...prev, stage]
);
};
-
+
const toggleInterest = (interest: string) => {
- setSelectedInterests(prev =>
+ setSelectedInterests((prev) =>
prev.includes(interest)
- ? prev.filter(i => i !== interest)
+ ? prev.filter((i) => i !== interest)
: [...prev, interest]
);
};
-
+
return (
Find Investors
-
Connect with investors who match your startup's needs
+
+ Connect with investors who match your startup's needs
+
-
+
{/* Filters sidebar */}
@@ -64,16 +93,18 @@ export const InvestorsPage: React.FC = () => {
-
Investment Stage
+
+ Investment Stage
+
- {allStages.map(stage => (
+ {allStages.map((stage) => (
toggleStage(stage)}
className={`block w-full text-left px-3 py-2 rounded-md text-sm ${
selectedStages.includes(stage)
- ? 'bg-primary-50 text-primary-700'
- : 'text-gray-700 hover:bg-gray-50'
+ ? "bg-primary-50 text-primary-700"
+ : "text-gray-700 hover:bg-gray-50"
}`}
>
{stage}
@@ -81,14 +112,20 @@ export const InvestorsPage: React.FC = () => {
))}
-
+
-
Investment Interests
+
+ Investment Interests
+
- {allInterests.map(interest => (
+ {allInterests.map((interest) => (
toggleInterest(interest)}
>
@@ -97,9 +134,11 @@ export const InvestorsPage: React.FC = () => {
))}
-
+
-
Location
+
+ Location
+
@@ -118,7 +157,7 @@ export const InvestorsPage: React.FC = () => {
-
+
{/* Main content */}
@@ -129,7 +168,7 @@ export const InvestorsPage: React.FC = () => {
startAdornment={
}
fullWidth
/>
-
+
@@ -137,17 +176,14 @@ export const InvestorsPage: React.FC = () => {
-
+
- {filteredInvestors.map(investor => (
-
+ {filteredInvestors.map((investor) => (
+
))}
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/messages/MessagesPage.tsx b/src/pages/messages/MessagesPage.tsx
index c7589fb80..070249a2b 100644
--- a/src/pages/messages/MessagesPage.tsx
+++ b/src/pages/messages/MessagesPage.tsx
@@ -11,7 +11,7 @@ export const MessagesPage: React.FC = () => {
if (!user) return null;
- const conversations = getConversationsForUser(user.id);
+ const conversations = getConversationsForUser(user.userId);
return (
diff --git a/src/pages/profile/EntrepreneurProfile.tsx b/src/pages/profile/EntrepreneurProfile.tsx
index 6b633bbe2..b354735fb 100644
--- a/src/pages/profile/EntrepreneurProfile.tsx
+++ b/src/pages/profile/EntrepreneurProfile.tsx
@@ -29,7 +29,7 @@ export const EntrepreneurProfile: React.FC = () => {
const { user: currentUser } = useAuth();
const [isEditing, setIsEditing] = useState(false);
const [entrepreneur, setEnterpreneur] = useState
();
- const initalData = {
+ const initialData = {
userId: id,
startupName: entrepreneur?.startupName,
pitchSummary: entrepreneur?.pitchSummary,
@@ -37,26 +37,28 @@ export const EntrepreneurProfile: React.FC = () => {
industry: entrepreneur?.industry,
foundedYear: entrepreneur?.foundedYear,
teamSize: entrepreneur?.teamSize,
+ minValuation: entrepreneur?.minValuation,
+ maxValuation: entrepreneur?.maxValuation,
+ marketOpportunity: entrepreneur?.marketOpportunity,
+ advantage: entrepreneur?.advantage,
};
- const [formData, setFormData] = useState(initalData);
+ const [formData, setFormData] = useState(initialData);
// Fetch entrepreneur data
useEffect(() => {
const fetchEntrepreneur = async () => {
const entrepreneur = await getEnterpreneurById(id);
- console.log(entrepreneur);
- console.log(currentUser)
setEnterpreneur(entrepreneur);
};
fetchEntrepreneur();
}, []);
useEffect(() => {
- setFormData(initalData);
+ setFormData(initialData);
}, [entrepreneur]);
if (!currentUser) return null;
- if (!entrepreneur) {
+ if (!entrepreneur || entrepreneur.role !== "entrepreneur") {
return (
@@ -75,7 +77,8 @@ export const EntrepreneurProfile: React.FC = () => {
);
}
- const isCurrentUser = currentUser?.userId === entrepreneur.userId;
+ const isCurrentUser =
+ currentUser?.userId === (entrepreneur.userId || entrepreneur._id);
const isInvestor = currentUser?.role === "investor";
// Check if the current investor has already sent a request to this entrepreneur
@@ -93,10 +96,6 @@ export const EntrepreneurProfile: React.FC = () => {
id,
`I'm interested in learning more about ${entrepreneur.startupName} and would like to explore potential investment opportunities.`
);
-
- // In a real app, we would refresh the data or update state
- // For this demo, we'll force a page reload
- window.location.reload();
}
};
@@ -108,7 +107,32 @@ export const EntrepreneurProfile: React.FC = () => {
const handleSubmit = async (e) => {
e.preventDefault();
updateEntrepreneurData(formData);
- setFormData(initalData);
+ const {
+ startupName,
+ pitchSummary,
+ fundingNeeded,
+ industry,
+ foundedYear,
+ teamSize,
+ marketOpportunity,
+ advantage,
+ minValuation,
+ maxValuation,
+ } = formData;
+ setEnterpreneur({
+ ...entrepreneur,
+ startupName,
+ pitchSummary,
+ fundingNeeded,
+ industry,
+ foundedYear,
+ teamSize,
+ marketOpportunity,
+ advantage,
+ minValuation,
+ maxValuation,
+ });
+ setFormData(initialData);
setIsEditing(false);
};
@@ -116,44 +140,81 @@ export const EntrepreneurProfile: React.FC = () => {
{isEditing && (
-
@@ -260,33 +554,23 @@ export const InvestorProfile: React.FC = () => {
Investment Focus
-
-
-
-
HealthTech
-
+ {investor.investmentInterests &&
+ investor.investmentInterests.map((interest, index) => (
-
-
+ key={index}
+ className="flex justify-between items-center"
+ >
+
+ {interest}
+
+
+
+ ))}
@@ -309,7 +593,7 @@ export const InvestorProfile: React.FC = () => {
Successful Exits
- 4
+ {formData.successfullExits}
@@ -337,7 +621,8 @@ export const InvestorProfile: React.FC = () => {
Active Investments
- {investor.portfolioCompanies && investor.portfolioCompanies.length}
+ {investor.portfolioCompanies &&
+ investor.portfolioCompanies.length}
diff --git a/src/types/index.ts b/src/types/index.ts
index 355b88cf1..8f782b5c8 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1,4 +1,4 @@
-export type UserRole = 'entrepreneur' | 'investor';
+export type UserRole = "entrepreneur" | "investor";
export interface User {
userId: string;
@@ -6,28 +6,35 @@ export interface User {
email: string;
role: UserRole;
avatarUrl: string;
- location:string;
+ location: string;
bio: string;
isOnline?: boolean;
}
export interface Entrepreneur extends User {
- startupName: string;
- pitchSummary: string;
- fundingNeeded: string;
- industry: string;
- foundedYear: number;
- teamSize: number;
+ startupName: string | undefined;
+ pitchSummary: string | undefined;
+ fundingNeeded: string | undefined;
+ industry: string | undefined;
+ foundedYear: number | undefined;
+ teamSize: number | undefined;
+ minValuation:string | undefined;
+ maxValuation:string | undefined;
+ marketOpportunity:string | undefined;
+ advantage:string | undefined;
}
export interface Investor extends User {
- role: 'investor';
- investmentInterests: string[];
- investmentStage: string[];
- portfolioCompanies: string[];
- totalInvestments: number;
- minimumInvestment: string;
- maximumInvestment: string;
+ investmentInterests: string[] | undefined;
+ investmentStage: string[] | undefined;
+ portfolioCompanies: string[] | undefined;
+ totalInvestments: number | undefined;
+ minimumInvestment: string | undefined;
+ maximumInvestment: string | undefined;
+ investmentCriteria: string[] | undefined;
+ successfullExits: number | undefined;
+ minTimline:number | undefined,
+ maxTimline:number | undefined,
}
export interface Message {
@@ -51,7 +58,7 @@ export interface CollaborationRequest {
investorId: string;
entrepreneurId: string;
message: string;
- status: 'pending' | 'accepted' | 'rejected';
+ status: "pending" | "accepted" | "rejected";
createdAt: string;
}
@@ -68,13 +75,18 @@ export interface Document {
export interface AuthContextType {
user: User | null;
- userData:User | null;
+ userData: User | null;
login: (email: string, password: string, role: UserRole) => Promise
;
- register: (name: string, email: string, password: string, role: UserRole) => Promise;
+ register: (
+ name: string,
+ email: string,
+ password: string,
+ role: UserRole
+ ) => Promise;
logout: () => void;
forgotPassword: (email: string) => Promise;
resetPassword: (token: string, newPassword: string) => Promise;
updateProfile: (userId: string, updates: Partial) => Promise;
isAuthenticated: boolean;
isLoading: boolean;
-}
\ No newline at end of file
+}
From 0f360417676d129583c6b775e2c4695e5d08c0b8 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Sat, 30 Aug 2025 22:34:27 +0500
Subject: [PATCH 06/49] setting up the socket.io
---
package-lock.json | 92 +++++++++++++++++-
package.json | 3 +-
src/App.tsx | 1 -
src/components/investor/InvestorCard.tsx | 4 +-
src/data/messages.ts | 47 ++++-----
src/data/users.ts | 13 ++-
src/pages/chat/ChatPage.tsx | 119 +++++++++++++----------
7 files changed, 190 insertions(+), 89 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 2f89c6b59..7d980fc21 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,8 @@
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.4.1",
- "react-router-dom": "^6.22.1"
+ "react-router-dom": "^6.22.1",
+ "socket.io-client": "^4.8.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
@@ -1218,6 +1219,12 @@
"win32"
]
},
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1966,7 +1973,6 @@
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "dev": true,
"dependencies": {
"ms": "^2.1.3"
},
@@ -2038,6 +2044,28 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
+ "node_modules/engine.io-client": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1",
+ "xmlhttprequest-ssl": "~2.1.1"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -3149,8 +3177,7 @@
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/mz": {
"version": "2.7.0",
@@ -3854,6 +3881,34 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/socket.io-client": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -4423,6 +4478,35 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index e9bf404e9..a9be5f4f6 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,8 @@
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-hot-toast": "^2.4.1",
- "react-router-dom": "^6.22.1"
+ "react-router-dom": "^6.22.1",
+ "socket.io-client": "^4.8.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
diff --git a/src/App.tsx b/src/App.tsx
index d35daa896..c3684194b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -89,7 +89,6 @@ function App() {
{/* Chat Routes */}
}>
- } />
} />
diff --git a/src/components/investor/InvestorCard.tsx b/src/components/investor/InvestorCard.tsx
index 756da07d2..42bb0d59a 100644
--- a/src/components/investor/InvestorCard.tsx
+++ b/src/components/investor/InvestorCard.tsx
@@ -19,12 +19,12 @@ export const InvestorCard: React.FC = ({
const navigate = useNavigate();
const handleViewProfile = () => {
- navigate(`/profile/investor/${investor._id}`);
+ navigate(`/profile/investor/${investor.userId || investor._id}`);
};
const handleMessage = (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent card click
- navigate(`/chat/${investor.id}`);
+ navigate(`/chat/${investor.userId || investor._id}`);
};
return (
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 895d3dcd7..2cc6d9b55 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -1,3 +1,4 @@
+import axios from 'axios';
import { Message, ChatConversation } from '../types';
export const messages: Message[] = [
@@ -88,41 +89,29 @@ export const messages: Message[] = [
}
];
+const URL = "http://localhost:5000";
// Helper function to get messages between two users
-export const getMessagesBetweenUsers = (user1Id: string, user2Id: string): Message[] => {
- return messages.filter(
- message =>
- (message.senderId === user1Id && message.receiverId === user2Id) ||
- (message.senderId === user2Id && message.receiverId === user1Id)
- ).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
+export const getMessagesBetweenUsers = async(user1Id: string, user2Id: string): Message[] => {
+ const res = await axios.get(`${URL}/message/get-message-btw-users/sender?=${user1Id}receiver?=${user2Id}`,{
+ withCredentials:true
+ });
+ const {messages} = res.data;
+ return messages;
};
+export const saveMessagesBetweenUsers = async(messages:Any)=> {
+ await axios.post(`${URL}/message/save-message-btw-users`,messages,{
+ withCredentials:true
+ });
+};
// Helper function to get conversations for a user
-export const getConversationsForUser = (userId: string): ChatConversation[] => {
+export const getConversationsForUser = async(userId: string): ChatConversation[] => {
// Get unique conversation partners
- const conversationPartners = new Set();
-
- messages.forEach(message => {
- if (message.senderId === userId) {
- conversationPartners.add(message.receiverId);
- }
- if (message.receiverId === userId) {
- conversationPartners.add(message.senderId);
- }
+ const res = await axios.get(`${URL}/conversation/get-conversations-for-user/${userId}`,{
+ withCredentials:true
});
-
- // Create conversation objects
- return Array.from(conversationPartners).map(partnerId => {
- const conversationMessages = getMessagesBetweenUsers(userId, partnerId);
- const lastMessage = conversationMessages[conversationMessages.length - 1];
-
- return {
- id: `conv-${userId}-${partnerId}`,
- participants: [userId, partnerId],
- lastMessage,
- updatedAt: lastMessage?.timestamp || new Date().toISOString()
- };
- }).sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
+ const {conversation} = res.data;
+ return conversation;
};
// Helper function to send a new message
diff --git a/src/data/users.ts b/src/data/users.ts
index 04e1b4556..c96322810 100644
--- a/src/data/users.ts
+++ b/src/data/users.ts
@@ -1,5 +1,4 @@
import axios from "axios";
-import { Entrepreneur, Investor } from "../types";
import toast from "react-hot-toast";
const URL = "http://localhost:5000";
@@ -47,7 +46,7 @@ export const getEnterpreneurById = async (id) => {
withCredentials: true,
}
);
- const {entrepreneur} = res.data;
+ const { entrepreneur } = res.data;
console.log(entrepreneur);
return entrepreneur;
} catch (err) {
@@ -84,3 +83,13 @@ export const updateInvestorData = async (formData: any) => {
console.log(err);
}
};
+
+export const getUserFromDb = async (id) => {
+ try {
+ const res = await axios.get(`${URL}/user/get-user-by-id/${id}`);
+ const { user } = res.data;
+ return user;
+ } catch (err) {
+ console.log(err);
+ }
+};
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 9c99583c3..1eb8fb6f7 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -1,72 +1,83 @@
-import React, { useState, useEffect, useRef } from 'react';
-import { useParams } from 'react-router-dom';
-import { Send, Phone, Video, Info, Smile } from 'lucide-react';
-import { Avatar } from '../../components/ui/Avatar';
-import { Button } from '../../components/ui/Button';
-import { Input } from '../../components/ui/Input';
-import { ChatMessage } from '../../components/chat/ChatMessage';
-import { ChatUserList } from '../../components/chat/ChatUserList';
-import { useAuth } from '../../context/AuthContext';
-import { Message } from '../../types';
-import { getMessagesBetweenUsers, sendMessage, getConversationsForUser } from '../../data/messages';
-import { MessageCircle } from 'lucide-react';
+import React, { useState, useEffect, useRef } from "react";
+import { useParams } from "react-router-dom";
+import { Send, Phone, Video, Info, Smile } from "lucide-react";
+import { Avatar } from "../../components/ui/Avatar";
+import { Button } from "../../components/ui/Button";
+import { Input } from "../../components/ui/Input";
+import { ChatMessage } from "../../components/chat/ChatMessage";
+import { ChatUserList } from "../../components/chat/ChatUserList";
+import { useAuth } from "../../context/AuthContext";
+import { Message, User } from "../../types";
+import {
+ getMessagesBetweenUsers,
+ sendMessage,
+ getConversationsForUser,
+} from "../../data/messages";
+import { MessageCircle } from "lucide-react";
+import { getUserFromDb } from "../../data/users";
export const ChatPage: React.FC = () => {
const { userId } = useParams<{ userId: string }>();
const { user: currentUser } = useAuth();
const [messages, setMessages] = useState([]);
- const [newMessage, setNewMessage] = useState('');
+ const [newMessage, setNewMessage] = useState("");
const [conversations, setConversations] = useState([]);
const messagesEndRef = useRef(null);
-
- const chatPartner = userId ? findUserById(userId) : null;
-
+ const [chatPartner, setChatPartner] = useState(null);
+
useEffect(() => {
+ // Load user Data
+ const fetchUserData = async () => {
+ const Partner = await getUserFromDb(userId);
+ setChatPartner(Partner || null);
+ };
+ fetchUserData();
+
// Load conversations
if (currentUser) {
- setConversations(getConversationsForUser(currentUser.id));
+ setConversations(getConversationsForUser(currentUser.userId));
}
}, [currentUser]);
-
+
useEffect(() => {
// Load messages between users
if (currentUser && userId) {
- setMessages(getMessagesBetweenUsers(currentUser.id, userId));
+ setMessages(getMessagesBetweenUsers(currentUser.userId, userId));
}
}, [currentUser, userId]);
-
+
useEffect(() => {
// Scroll to bottom of messages
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
-
+
const handleSendMessage = (e: React.FormEvent) => {
e.preventDefault();
-
+
if (!newMessage.trim() || !currentUser || !userId) return;
-
+
const message = sendMessage({
- senderId: currentUser.id,
+ senderId: currentUser.userId,
receiverId: userId,
- content: newMessage
+ content: newMessage,
});
-
+
setMessages([...messages, message]);
- setNewMessage('');
-
+ setNewMessage("");
+
// Update conversations
- setConversations(getConversationsForUser(currentUser.id));
+ setConversations(getConversationsForUser(currentUser.userId));
};
-
+
if (!currentUser) return null;
-
+
return (
{/* Conversations sidebar */}
-
+
{/* Main chat area */}
{/* Chat header */}
@@ -78,18 +89,20 @@ export const ChatPage: React.FC = () => {
src={chatPartner.avatarUrl}
alt={chatPartner.name}
size="md"
- status={chatPartner.isOnline ? 'online' : 'offline'}
+ status={chatPartner.isOnline ? "online" : "offline"}
className="mr-3"
/>
-
+
-
{chatPartner.name}
+
+ {chatPartner.name}
+
- {chatPartner.isOnline ? 'Online' : 'Last seen recently'}
+ {chatPartner.isOnline ? "Online" : "Last seen recently"}
-
+
-
+
{/* Messages container */}
{messages.length > 0 ? (
- {messages.map(message => (
+ {messages.map((message) => (
))}
@@ -138,12 +151,16 @@ export const ChatPage: React.FC = () => {
-
No messages yet
-
Send a message to start the conversation
+
+ No messages yet
+
+
+ Send a message to start the conversation
+
)}
-
+
{/* Message input */}
);
-};
\ No newline at end of file
+};
From f7b758df74cddd36ae21e7e6cc9ce0696a57a0ca Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Sun, 31 Aug 2025 23:22:50 +0500
Subject: [PATCH 07/49] real chat enabled
---
src/components/chat/ChatMessage.tsx | 17 ++-
src/data/messages.ts | 195 +++++++++++++++-------------
src/pages/chat/ChatPage.tsx | 102 ++++++++++++---
src/types/index.ts | 2 -
4 files changed, 202 insertions(+), 114 deletions(-)
diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx
index d24ae5ba6..5da1fac80 100644
--- a/src/components/chat/ChatMessage.tsx
+++ b/src/components/chat/ChatMessage.tsx
@@ -1,7 +1,8 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { formatDistanceToNow } from 'date-fns';
-import { Message } from '../../types';
+import { Message, User } from '../../types';
import { Avatar } from '../ui/Avatar';
+import { getUserFromDb } from '../../data/users';
interface ChatMessageProps {
message: Message;
@@ -9,7 +10,15 @@ interface ChatMessageProps {
}
export const ChatMessage: React.FC = ({ message, isCurrentUser }) => {
- const user = findUserById(message.senderId);
+ const [user, setUser] = useState(null);
+ useEffect(() => {
+ // Load partner Data
+ const fetchUserData = async () => {
+ const user = await getUserFromDb(message.senderId);
+ setUser(user || null);
+ };
+ fetchUserData();
+ }, []);
if (!user) return null;
@@ -38,7 +47,7 @@ export const ChatMessage: React.FC = ({ message, isCurrentUser
- {formatDistanceToNow(new Date(message.timestamp), { addSuffix: true })}
+ {formatDistanceToNow(new Date(message.time), { addSuffix: true })}
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 2cc6d9b55..9b2f32506 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -1,128 +1,143 @@
-import axios from 'axios';
-import { Message, ChatConversation } from '../types';
+import axios from "axios";
+import { Message, ChatConversation } from "../types";
export const messages: Message[] = [
// Conversation between Sarah (e1) and Michael (i1)
{
- id: 'm1',
- senderId: 'e1',
- receiverId: 'i1',
- content: 'Thanks for connecting. Id love to discuss how our AI platform can revolutionize financial analytics for SMBs.',
- timestamp: '2023-08-15T10:15:00Z',
- isRead: true
+ id: "m1",
+ senderId: "e1",
+ receiverId: "i1",
+ content:
+ "Thanks for connecting. Id love to discuss how our AI platform can revolutionize financial analytics for SMBs.",
+ timestamp: "2023-08-15T10:15:00Z",
+ isRead: true,
},
{
- id: 'm2',
- senderId: 'i1',
- receiverId: 'e1',
- content: 'Im interested in learning more about your tech stack and ML models. Are you available for a call this week?',
- timestamp: '2023-08-15T10:30:00Z',
- isRead: true
+ id: "m2",
+ senderId: "i1",
+ receiverId: "e1",
+ content:
+ "Im interested in learning more about your tech stack and ML models. Are you available for a call this week?",
+ timestamp: "2023-08-15T10:30:00Z",
+ isRead: true,
},
{
- id: 'm3',
- senderId: 'e1',
- receiverId: 'i1',
- content: 'Absolutely! I can walk you through our technology and current traction. How does Thursday at 2pm PT work?',
- timestamp: '2023-08-15T10:45:00Z',
- isRead: true
+ id: "m3",
+ senderId: "e1",
+ receiverId: "i1",
+ content:
+ "Absolutely! I can walk you through our technology and current traction. How does Thursday at 2pm PT work?",
+ timestamp: "2023-08-15T10:45:00Z",
+ isRead: true,
},
{
- id: 'm4',
- senderId: 'i1',
- receiverId: 'e1',
- content: 'Thursday works great. Ill send a calendar invite. Looking forward to it!',
- timestamp: '2023-08-15T11:00:00Z',
- isRead: false
+ id: "m4",
+ senderId: "i1",
+ receiverId: "e1",
+ content:
+ "Thursday works great. Ill send a calendar invite. Looking forward to it!",
+ timestamp: "2023-08-15T11:00:00Z",
+ isRead: false,
},
// Conversation between Maya (e3) and Jennifer (i2)
{
- id: 'm5',
- senderId: 'i2',
- receiverId: 'e3',
- content: 'I saw your pitch for HealthPulse and Im intrigued by your approach to mental healthcare accessibility.',
- timestamp: '2023-08-16T09:00:00Z',
- isRead: true
+ id: "m5",
+ senderId: "i2",
+ receiverId: "e3",
+ content:
+ "I saw your pitch for HealthPulse and Im intrigued by your approach to mental healthcare accessibility.",
+ timestamp: "2023-08-16T09:00:00Z",
+ isRead: true,
},
{
- id: 'm6',
- senderId: 'e3',
- receiverId: 'i2',
- content: 'Thank you, Jennifer! Mental health services need to be more accessible, especially in underserved communities.',
- timestamp: '2023-08-16T09:15:00Z',
- isRead: true
+ id: "m6",
+ senderId: "e3",
+ receiverId: "i2",
+ content:
+ "Thank you, Jennifer! Mental health services need to be more accessible, especially in underserved communities.",
+ timestamp: "2023-08-16T09:15:00Z",
+ isRead: true,
},
{
- id: 'm7',
- senderId: 'i2',
- receiverId: 'e3',
- content: 'I completely agree. Could you share more about your user acquisition strategy and current metrics?',
- timestamp: '2023-08-16T09:30:00Z',
- isRead: false
+ id: "m7",
+ senderId: "i2",
+ receiverId: "e3",
+ content:
+ "I completely agree. Could you share more about your user acquisition strategy and current metrics?",
+ timestamp: "2023-08-16T09:30:00Z",
+ isRead: false,
},
// Conversation between David (e2) and Robert (i3)
{
- id: 'm8',
- senderId: 'e2',
- receiverId: 'i3',
- content: 'Hello Robert, I noticed you invest in healthcare. While GreenLife is focused on sustainable packaging, we have some applications in medical supplies.',
- timestamp: '2023-08-17T14:00:00Z',
- isRead: true
+ id: "m8",
+ senderId: "e2",
+ receiverId: "i3",
+ content:
+ "Hello Robert, I noticed you invest in healthcare. While GreenLife is focused on sustainable packaging, we have some applications in medical supplies.",
+ timestamp: "2023-08-17T14:00:00Z",
+ isRead: true,
},
{
- id: 'm9',
- senderId: 'i3',
- receiverId: 'e2',
- content: 'Interesting crossover, David. Id be interested in learning more about your biodegradable materials and how they could be used in healthcare.',
- timestamp: '2023-08-17T15:30:00Z',
- isRead: true
+ id: "m9",
+ senderId: "i3",
+ receiverId: "e2",
+ content:
+ "Interesting crossover, David. Id be interested in learning more about your biodegradable materials and how they could be used in healthcare.",
+ timestamp: "2023-08-17T15:30:00Z",
+ isRead: true,
},
{
- id: 'm10',
- senderId: 'e2',
- receiverId: 'i3',
- content: 'Great! Weve been developing materials that can safely package medical devices while being eco-friendly. Our tests show 40% less environmental impact.',
- timestamp: '2023-08-17T16:45:00Z',
- isRead: false
- }
+ id: "m10",
+ senderId: "e2",
+ receiverId: "i3",
+ content:
+ "Great! Weve been developing materials that can safely package medical devices while being eco-friendly. Our tests show 40% less environmental impact.",
+ timestamp: "2023-08-17T16:45:00Z",
+ isRead: false,
+ },
];
const URL = "http://localhost:5000";
// Helper function to get messages between two users
-export const getMessagesBetweenUsers = async(user1Id: string, user2Id: string): Message[] => {
- const res = await axios.get(`${URL}/message/get-message-btw-users/sender?=${user1Id}receiver?=${user2Id}`,{
- withCredentials:true
- });
- const {messages} = res.data;
+export const getMessagesBetweenUsers = async (
+ user1Id: string,
+ user2Id: string
+) => {
+ const res = await axios.get(
+ `${URL}/message/get-messages-btw-users?sender=${user1Id}&receiver=${user2Id}`,
+ {
+ withCredentials: true,
+ }
+ );
+ const { messages } = res.data;
return messages;
};
-export const saveMessagesBetweenUsers = async(messages:Any)=> {
- await axios.post(`${URL}/message/save-message-btw-users`,messages,{
- withCredentials:true
- });
+export const saveMessagesBetweenUsers = async (newMessage: Any) => {
+ try {
+ const res = await axios.post(`${URL}/message/save-message`, newMessage, {
+ withCredentials: true,
+ });
+ console.log("message saved");
+
+ const { message } = res.data;
+ return message;
+ } catch (err) {
+ console.log(err);
+ }
};
// Helper function to get conversations for a user
-export const getConversationsForUser = async(userId: string): ChatConversation[] => {
+export const getConversationsForUser = async (userId: string): any[] => {
// Get unique conversation partners
- const res = await axios.get(`${URL}/conversation/get-conversations-for-user/${userId}`,{
- withCredentials:true
- });
- const {conversation} = res.data;
- return conversation;
+ const res = await axios.get(
+ `${URL}/conversation/get-conversations-for-user/${userId}`,
+ {
+ withCredentials: true,
+ }
+ );
+ const { conversations } = res.data;
+ return conversations;
};
-// Helper function to send a new message
-export const sendMessage = (newMessage: Omit): Message => {
- const message: Message = {
- ...newMessage,
- id: `m${messages.length + 1}`,
- timestamp: new Date().toISOString(),
- isRead: false
- };
-
- messages.push(message);
- return message;
-};
\ No newline at end of file
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 1eb8fb6f7..ba6b394f4 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -10,11 +10,12 @@ import { useAuth } from "../../context/AuthContext";
import { Message, User } from "../../types";
import {
getMessagesBetweenUsers,
- sendMessage,
getConversationsForUser,
+ saveMessagesBetweenUsers,
} from "../../data/messages";
import { MessageCircle } from "lucide-react";
import { getUserFromDb } from "../../data/users";
+import { io } from "socket.io-client";
export const ChatPage: React.FC = () => {
const { userId } = useParams<{ userId: string }>();
@@ -24,49 +25,114 @@ export const ChatPage: React.FC = () => {
const [conversations, setConversations] = useState([]);
const messagesEndRef = useRef(null);
const [chatPartner, setChatPartner] = useState(null);
+ const socket = useRef();
useEffect(() => {
- // Load user Data
+ // Load partner Data
const fetchUserData = async () => {
const Partner = await getUserFromDb(userId);
setChatPartner(Partner || null);
};
fetchUserData();
- // Load conversations
- if (currentUser) {
- setConversations(getConversationsForUser(currentUser.userId));
- }
- }, [currentUser]);
+ // Load messages
+ const fetchMessages = async () => {
+ if (currentUser && userId) {
+ const messages = await getMessagesBetweenUsers(
+ currentUser?.userId,
+ userId
+ );
+ setMessages(messages.length > 0 ? messages : []);
+ }
+ };
+ fetchMessages();
+ }, []);
+ // Load conversations
useEffect(() => {
- // Load messages between users
- if (currentUser && userId) {
- setMessages(getMessagesBetweenUsers(currentUser.userId, userId));
- }
- }, [currentUser, userId]);
+ const fetchConversations = async () => {
+ if (currentUser) {
+ const conversations = getConversationsForUser(currentUser.userId);
+ setConversations(conversations.length > 0 ? conversations : []);
+ }
+ };
+ fetchConversations();
+ }, [currentUser?.userId]);
+
+ // Load messages between users
+ // useEffect(() => {
+ // const fetchMessages = async () => {
+ // if (currentUser && userId) {
+ // const messages = await getMessagesBetweenUsers(
+ // currentUser?.userId,
+ // userId
+ // );
+ // setMessages(messages.length > 0 ? messages : []);
+ // }
+ // };
+ // fetchMessages();
+ // },[]);
+
+ // connect socket.io client
+ useEffect(() => {
+ socket.current = io("http://localhost:5000", {
+ withCredentials: true,
+ });
+
+ const handleConnect = () => {
+ socket.current.emit("join", currentUser?.userId);
+ };
+
+ // connect user
+ socket.current.on("connect", handleConnect);
+
+ // when user receive message
+ socket.current.on("received-message", (message) => {
+ console.log(message);
+ console.log(messages);
+ setMessages((prev) => [...prev, message]);
+ });
+
+ // when user got hi
+ socket.current.on("hi", () => {
+ alert("hi");
+ });
+
+ return () => {
+ if (socket.current) {
+ socket.current.off("connect", handleConnect);
+ socket.current.off("hi");
+ socket.current.disconnect();
+ }
+ };
+ }, [currentUser?.userId]);
useEffect(() => {
// Scroll to bottom of messages
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
- const handleSendMessage = (e: React.FormEvent) => {
+ // Hanlde send message
+ const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!newMessage.trim() || !currentUser || !userId) return;
- const message = sendMessage({
+ const message = {
senderId: currentUser.userId,
receiverId: userId,
content: newMessage,
- });
+ isRead: false,
+ };
+ const msg = await saveMessagesBetweenUsers(message);
+ socket.current.emit("send-message", msg);
+ setMessages((prev) => [...prev, msg]);
- setMessages([...messages, message]);
setNewMessage("");
// Update conversations
- setConversations(getConversationsForUser(currentUser.userId));
+ const conversations = getConversationsForUser(currentUser.userId);
+ setConversations(conversations.length > 0 ? conversations : []);
};
if (!currentUser) return null;
@@ -139,7 +205,7 @@ export const ChatPage: React.FC = () => {
{messages.map((message) => (
diff --git a/src/types/index.ts b/src/types/index.ts
index 8f782b5c8..dabae5ede 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -38,11 +38,9 @@ export interface Investor extends User {
}
export interface Message {
- id: string;
senderId: string;
receiverId: string;
content: string;
- timestamp: string;
isRead: boolean;
}
From 58d087cb7998bebe27385c50c032e3082a7f1c58 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 2 Sep 2025 00:00:52 +0500
Subject: [PATCH 08/49] conversations will update dynamically
---
src/components/chat/ChatUserList.tsx | 112 +++++++++++++++++----------
src/components/ui/Badge.tsx | 2 +-
src/data/messages.ts | 18 ++++-
src/pages/auth/LoginPage.tsx | 8 +-
src/pages/chat/ChatPage.tsx | 82 ++++++++++----------
5 files changed, 134 insertions(+), 88 deletions(-)
diff --git a/src/components/chat/ChatUserList.tsx b/src/components/chat/ChatUserList.tsx
index 6f1fb52f6..970568d5d 100644
--- a/src/components/chat/ChatUserList.tsx
+++ b/src/components/chat/ChatUserList.tsx
@@ -1,22 +1,46 @@
-import React from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
-import { formatDistanceToNow } from 'date-fns';
-import { ChatConversation } from '../../types';
-import { Avatar } from '../ui/Avatar';
-import { Badge } from '../ui/Badge';
-import { useAuth } from '../../context/AuthContext';
+import React, { useEffect, useState } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { formatDistanceToNow } from "date-fns";
+import { ChatConversation, User } from "../../types";
+import { Avatar } from "../ui/Avatar";
+import { Badge } from "../ui/Badge";
+import { useAuth } from "../../context/AuthContext";
+import { getUserFromDb } from "../../data/users";
interface ChatUserListProps {
- conversations: ChatConversation[];
+ conversation: ChatConversation;
}
-export const ChatUserList: React.FC = ({ conversations }) => {
+export const ChatUserList: React.FC = ({ conversation }) => {
const navigate = useNavigate();
const { userId: activeUserId } = useParams<{ userId: string }>();
const { user: currentUser } = useAuth();
-
+
+ const [participants, setParticipants] = useState([]);
+
+ useEffect(() => {
+ const fetchParticipants = async () => {
+ if (!conversation?.participants) return;
+
+ // filter out current user
+ const otherIds = conversation.participants.filter(
+ (id) => id !== currentUser?.userId
+ );
+
+ // fetch all users in parallel
+ const users = await Promise.all(
+ otherIds.map(async (id) => await getUserFromDb(id))
+ );
+
+ // filter nulls (if any user not found)
+ setParticipants(users.filter(Boolean) as User[]);
+ };
+
+ fetchParticipants();
+ }, [conversation, currentUser]);
+
+ if (conversation === null) return;
if (!currentUser) return null;
-
const handleSelectUser = (userId: string) => {
navigate(`/chat/${userId}`);
};
@@ -24,63 +48,67 @@ export const ChatUserList: React.FC = ({ conversations }) =>
return (
-
Messages
-
+
+ Messages
+
+
- {conversations.length > 0 ? (
- conversations.map(conversation => {
- // Get the other participant (not the current user)
- const otherParticipantId = conversation.participants.find(id => id !== currentUser.id);
- if (!otherParticipantId) return null;
-
- const otherUser = findUserById(otherParticipantId);
- if (!otherUser) return null;
-
+ {participants.length > 0 ? (
+ participants.map((user) => {
const lastMessage = conversation.lastMessage;
- const isActive = activeUserId === otherParticipantId;
-
+ const isActive = user._id === activeUserId; // highlight current open chat
+
return (
handleSelectUser(otherUser.id)}
+ onClick={() => handleSelectUser(user._id)}
>
-
+
- {otherUser.name}
+ {user.name}
-
+
{lastMessage && (
- {formatDistanceToNow(new Date(lastMessage.timestamp), { addSuffix: false })}
+ {formatDistanceToNow(
+ new Date(conversation?.lastModified),
+ { addSuffix: false }
+ )}
)}
-
+
{lastMessage && (
- {lastMessage.senderId === currentUser.id ? 'You: ' : ''}
+ {lastMessage.senderId === currentUser.userId
+ ? "You: "
+ : ""}
{lastMessage.content}
)}
-
- {lastMessage && !lastMessage.isRead && lastMessage.senderId !== currentUser.id && (
-
New
- )}
+
+ {lastMessage &&
+ !lastMessage.isRead &&
+ lastMessage.senderId !== currentUser.userId && (
+
+ New
+
+ )}
@@ -95,4 +123,4 @@ export const ChatUserList: React.FC
= ({ conversations }) =>
);
-};
\ No newline at end of file
+};
diff --git a/src/components/ui/Badge.tsx b/src/components/ui/Badge.tsx
index ee33fa675..6fd01081b 100644
--- a/src/components/ui/Badge.tsx
+++ b/src/components/ui/Badge.tsx
@@ -18,7 +18,7 @@ export const Badge: React.FC
= ({
size = 'md',
rounded = false,
className = '',
- onClick={},
+ onClick,
}) => {
const variantClasses = {
primary: 'bg-primary-100 text-primary-800',
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 9b2f32506..8ac7f3f13 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -137,7 +137,21 @@ export const getConversationsForUser = async (userId: string): any[] => {
withCredentials: true,
}
);
- const { conversations } = res.data;
- return conversations;
+ const { conversation } = res.data;
+ console.log(conversation)
+ return conversation;
};
+export const addConversationsForUser = async (con: object): any[] => {
+ // Get unique conversation partners
+ const res = await axios.post(
+ `${URL}/conversation/add-conversations-for-user`,
+ con,
+ {
+ withCredentials: true,
+ }
+ );
+ const { conversation } = res.data;
+ console.log(conversation)
+ return conversation;
+};
diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx
index e95bff70a..898d57912 100644
--- a/src/pages/auth/LoginPage.tsx
+++ b/src/pages/auth/LoginPage.tsx
@@ -34,11 +34,11 @@ export const LoginPage: React.FC = () => {
// For demo purposes, pre-filled credentials
const fillDemoCredentials = (userRole: UserRole) => {
if (userRole === 'entrepreneur') {
- setEmail('sarah@techwave.io');
- setPassword('password123');
+ setEmail('en@gmail.com');
+ setPassword('123');
} else {
- setEmail('michael@vcinnovate.com');
- setPassword('password123');
+ setEmail('in@gmail.com');
+ setPassword('123');
}
setRole(userRole);
};
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index ba6b394f4..8310ade4f 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -12,6 +12,7 @@ import {
getMessagesBetweenUsers,
getConversationsForUser,
saveMessagesBetweenUsers,
+ addConversationsForUser,
} from "../../data/messages";
import { MessageCircle } from "lucide-react";
import { getUserFromDb } from "../../data/users";
@@ -22,21 +23,24 @@ export const ChatPage: React.FC = () => {
const { user: currentUser } = useAuth();
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
- const [conversations, setConversations] = useState([]);
+ const [conversation, setConversation] = useState();
const messagesEndRef = useRef(null);
const [chatPartner, setChatPartner] = useState(null);
const socket = useRef();
useEffect(() => {
- // Load partner Data
const fetchUserData = async () => {
+ // Load conversations
+ const conv = await getConversationsForUser(currentUser?.userId);
+ if (conv) {
+ setConversation(conv);
+ }
+
+ // Load partner Data
const Partner = await getUserFromDb(userId);
setChatPartner(Partner || null);
- };
- fetchUserData();
- // Load messages
- const fetchMessages = async () => {
+ // Load messages
if (currentUser && userId) {
const messages = await getMessagesBetweenUsers(
currentUser?.userId,
@@ -45,33 +49,9 @@ export const ChatPage: React.FC = () => {
setMessages(messages.length > 0 ? messages : []);
}
};
- fetchMessages();
- }, []);
-
- // Load conversations
- useEffect(() => {
- const fetchConversations = async () => {
- if (currentUser) {
- const conversations = getConversationsForUser(currentUser.userId);
- setConversations(conversations.length > 0 ? conversations : []);
- }
- };
- fetchConversations();
- }, [currentUser?.userId]);
+ fetchUserData();
+ }, [userId]);
- // Load messages between users
- // useEffect(() => {
- // const fetchMessages = async () => {
- // if (currentUser && userId) {
- // const messages = await getMessagesBetweenUsers(
- // currentUser?.userId,
- // userId
- // );
- // setMessages(messages.length > 0 ? messages : []);
- // }
- // };
- // fetchMessages();
- // },[]);
// connect socket.io client
useEffect(() => {
@@ -88,8 +68,6 @@ export const ChatPage: React.FC = () => {
// when user receive message
socket.current.on("received-message", (message) => {
- console.log(message);
- console.log(messages);
setMessages((prev) => [...prev, message]);
});
@@ -124,15 +102,41 @@ export const ChatPage: React.FC = () => {
content: newMessage,
isRead: false,
};
+
const msg = await saveMessagesBetweenUsers(message);
socket.current.emit("send-message", msg);
setMessages((prev) => [...prev, msg]);
-
setNewMessage("");
- // Update conversations
- const conversations = getConversationsForUser(currentUser.userId);
- setConversations(conversations.length > 0 ? conversations : []);
+ // Update conversation
+ try {
+ let updatedConv: any;
+
+ if (conversation && conversation.participants?.length > 0) {
+ for (const partner of conversation.participants) {
+ if (partner.userId !== userId) {
+ const updatedConv = await addConversationsForUser({
+ senderId: currentUser?.userId,
+ receiverId: chatPartner?._id,
+ lastMessage: msg,
+ });
+ setConversation(updatedConv);
+ }
+ }
+ } else {
+ updatedConv = await addConversationsForUser({
+ senderId: currentUser.userId,
+ receiverId: chatPartner?._id,
+ lastMessage: msg,
+ });
+ }
+ console.log(updatedConv)
+ if (updatedConv) {
+ setConversation(updatedConv);
+ }
+ } catch (err) {
+ console.error("Failed to update conversation", err);
+ }
};
if (!currentUser) return null;
@@ -141,7 +145,7 @@ export const ChatPage: React.FC = () => {
{/* Conversations sidebar */}
-
+
{/* Main chat area */}
From d3c69cf6b4fc6aee125c6430c95342e0b9362ef9 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 2 Sep 2025 22:44:31 +0500
Subject: [PATCH 09/49] dynamically messages changed
---
src/components/chat/ChatMessage.tsx | 14 +----
src/components/chat/ChatUserList.tsx | 21 ++++---
src/data/messages.ts | 23 +++++--
src/pages/chat/ChatPage.tsx | 89 ++++++++++++++++------------
4 files changed, 86 insertions(+), 61 deletions(-)
diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx
index 5da1fac80..da9803d65 100644
--- a/src/components/chat/ChatMessage.tsx
+++ b/src/components/chat/ChatMessage.tsx
@@ -2,24 +2,14 @@ import React, { useEffect, useState } from 'react';
import { formatDistanceToNow } from 'date-fns';
import { Message, User } from '../../types';
import { Avatar } from '../ui/Avatar';
-import { getUserFromDb } from '../../data/users';
interface ChatMessageProps {
message: Message;
+ user:User | undefined
isCurrentUser: boolean;
}
-export const ChatMessage: React.FC = ({ message, isCurrentUser }) => {
- const [user, setUser] = useState(null);
- useEffect(() => {
- // Load partner Data
- const fetchUserData = async () => {
- const user = await getUserFromDb(message.senderId);
- setUser(user || null);
- };
- fetchUserData();
- }, []);
-
+export const ChatMessage: React.FC = ({ message,user, isCurrentUser }) => {
if (!user) return null;
return (
diff --git a/src/components/chat/ChatUserList.tsx b/src/components/chat/ChatUserList.tsx
index 970568d5d..ec925fc87 100644
--- a/src/components/chat/ChatUserList.tsx
+++ b/src/components/chat/ChatUserList.tsx
@@ -17,27 +17,34 @@ export const ChatUserList: React.FC = ({ conversation }) => {
const { user: currentUser } = useAuth();
const [participants, setParticipants] = useState([]);
+ const [lastMessage, setLastMessage] = useState({});
useEffect(() => {
const fetchParticipants = async () => {
if (!conversation?.participants) return;
- // filter out current user
- const otherIds = conversation.participants.filter(
- (id) => id !== currentUser?.userId
+ const allIds = Array.from(
+ new Set(conversation.participants.flatMap((i) => i.receiverId || ""))
);
+ console.log(allIds);
// fetch all users in parallel
const users = await Promise.all(
- otherIds.map(async (id) => await getUserFromDb(id))
+ allIds.map(async (id) => await getUserFromDb(id))
);
// filter nulls (if any user not found)
setParticipants(users.filter(Boolean) as User[]);
+
+ const lastMessageIndex = conversation.participants.findIndex((part)=>{
+ return part.receiverId === activeUserId
+ });
+
+ setLastMessage({...conversation.participants[lastMessageIndex].lastMessage});
};
fetchParticipants();
- }, [conversation, currentUser]);
+ }, [conversation, currentUser,activeUserId]);
if (conversation === null) return;
if (!currentUser) return null;
@@ -55,7 +62,6 @@ export const ChatUserList: React.FC = ({ conversation }) => {
{participants.length > 0 ? (
participants.map((user) => {
- const lastMessage = conversation.lastMessage;
const isActive = user._id === activeUserId; // highlight current open chat
return (
@@ -79,7 +85,8 @@ export const ChatUserList: React.FC
= ({ conversation }) => {
- {user.name}
+ {user.name.slice(0, 5)}
+ {"..."}
{lastMessage && (
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 8ac7f3f13..4555c0be1 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -129,7 +129,7 @@ export const saveMessagesBetweenUsers = async (newMessage: Any) => {
}
};
// Helper function to get conversations for a user
-export const getConversationsForUser = async (userId: string): any[] => {
+export const getConversationsForUser = async (userId: string | undefined) => {
// Get unique conversation partners
const res = await axios.get(
`${URL}/conversation/get-conversations-for-user/${userId}`,
@@ -151,7 +151,22 @@ export const addConversationsForUser = async (con: object): any[] => {
withCredentials: true,
}
);
- const { conversation } = res.data;
- console.log(conversation)
- return conversation;
+ const { conversationForSender } = res.data;
+ console.log(conversationForSender)
+ return conversationForSender;
};
+
+
+export const updateConversationsForUser = async (con: object): any[] => {
+ // Get unique conversation partners
+ const res = await axios.post(
+ `${URL}/conversation/update-conversations-for-user`,
+ con,
+ {
+ withCredentials: true,
+ }
+ );
+ const { conversationForSender } = res.data;
+ console.log(conversationForSender)
+ return conversationForSender;
+};
\ No newline at end of file
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 8310ade4f..35d1a8dc3 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -12,7 +12,7 @@ import {
getMessagesBetweenUsers,
getConversationsForUser,
saveMessagesBetweenUsers,
- addConversationsForUser,
+ updateConversationsForUser,
} from "../../data/messages";
import { MessageCircle } from "lucide-react";
import { getUserFromDb } from "../../data/users";
@@ -26,32 +26,58 @@ export const ChatPage: React.FC = () => {
const [conversation, setConversation] = useState
();
const messagesEndRef = useRef(null);
const [chatPartner, setChatPartner] = useState(null);
+ const [users, setUsers] = useState<[string , User][]>([]);
const socket = useRef();
+ useEffect(()=>{
+ const fetchConversation = async()=>{
+ // Load conversations
+ const conv = await getConversationsForUser(currentUser?.userId);
+ if (conv) setConversation(conv);
+ }
+ fetchConversation();
+ },[userId,currentUser?.userId,messages])
+
useEffect(() => {
+ if (!currentUser?.userId || !userId) return; // guard clause
const fetchUserData = async () => {
- // Load conversations
- const conv = await getConversationsForUser(currentUser?.userId);
- if (conv) {
- setConversation(conv);
- }
+ try {
+
+ // Load partner Data
+ const partner = await getUserFromDb(userId);
+ setChatPartner(partner || null);
- // Load partner Data
- const Partner = await getUserFromDb(userId);
- setChatPartner(Partner || null);
-
- // Load messages
- if (currentUser && userId) {
+ // Load messages
const messages = await getMessagesBetweenUsers(
- currentUser?.userId,
+ currentUser.userId,
userId
);
setMessages(messages.length > 0 ? messages : []);
+ } catch (err) {
+ console.error("Error fetching user data:", err);
}
};
+
fetchUserData();
- }, [userId]);
+ }, [currentUser?.userId, userId]);
+
+ useEffect(() => {
+ const fetchUsers = async () => {
+ const uniqueIds = Array.from(new Set(messages.map((m) => m.senderId)));
+ const usersData = await Promise.all(
+ uniqueIds.map(async (id) => {
+ const user = await getUserFromDb(id);
+ return [id, user] as [string, User];
+ })
+ );
+
+ setUsers(Object.fromEntries(usersData));
+ };
+ if (messages.length > 0) {
+ fetchUsers();
+ }
+ }, [messages]);
// connect socket.io client
useEffect(() => {
@@ -110,27 +136,13 @@ export const ChatPage: React.FC = () => {
// Update conversation
try {
- let updatedConv: any;
-
- if (conversation && conversation.participants?.length > 0) {
- for (const partner of conversation.participants) {
- if (partner.userId !== userId) {
- const updatedConv = await addConversationsForUser({
- senderId: currentUser?.userId,
- receiverId: chatPartner?._id,
- lastMessage: msg,
- });
- setConversation(updatedConv);
- }
- }
- } else {
- updatedConv = await addConversationsForUser({
- senderId: currentUser.userId,
- receiverId: chatPartner?._id,
- lastMessage: msg,
- });
+ const con = {
+ senderId:currentUser?.userId,
+ receiverId:userId,
+ lastMessage:{...msg}
}
- console.log(updatedConv)
+ const updatedConv = await updateConversationsForUser(con);
+ console.log(updatedConv);
if (updatedConv) {
setConversation(updatedConv);
}
@@ -207,11 +219,12 @@ export const ChatPage: React.FC = () => {
{messages.length > 0 ? (
- {messages.map((message) => (
+ {messages.map((msg) => (
))}
From f8ec362f95f5ba44c232b77de44e2826c170ce65 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Wed, 3 Sep 2025 23:37:15 +0500
Subject: [PATCH 10/49] message read updation
---
src/components/chat/ChatMessage.tsx | 2 +-
src/components/chat/ChatUserList.tsx | 38 +++++++++++-------
src/data/messages.ts | 14 +++----
src/pages/chat/ChatPage.tsx | 60 +++++++++++++++++++---------
src/types/index.ts | 5 ++-
5 files changed, 75 insertions(+), 44 deletions(-)
diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx
index da9803d65..c238686d5 100644
--- a/src/components/chat/ChatMessage.tsx
+++ b/src/components/chat/ChatMessage.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import { formatDistanceToNow } from 'date-fns';
import { Message, User } from '../../types';
import { Avatar } from '../ui/Avatar';
diff --git a/src/components/chat/ChatUserList.tsx b/src/components/chat/ChatUserList.tsx
index ec925fc87..c98d9ea05 100644
--- a/src/components/chat/ChatUserList.tsx
+++ b/src/components/chat/ChatUserList.tsx
@@ -16,8 +16,7 @@ export const ChatUserList: React.FC = ({ conversation }) => {
const { userId: activeUserId } = useParams<{ userId: string }>();
const { user: currentUser } = useAuth();
- const [participants, setParticipants] = useState([]);
- const [lastMessage, setLastMessage] = useState({});
+ const [chatPartners, setChatPartners] = useState([]);
useEffect(() => {
const fetchParticipants = async () => {
@@ -27,27 +26,23 @@ export const ChatUserList: React.FC = ({ conversation }) => {
new Set(conversation.participants.flatMap((i) => i.receiverId || ""))
);
- console.log(allIds);
- // fetch all users in parallel
const users = await Promise.all(
allIds.map(async (id) => await getUserFromDb(id))
);
// filter nulls (if any user not found)
- setParticipants(users.filter(Boolean) as User[]);
+ setChatPartners(users.filter(Boolean) as User[]);
- const lastMessageIndex = conversation.participants.findIndex((part)=>{
- return part.receiverId === activeUserId
- });
-
- setLastMessage({...conversation.participants[lastMessageIndex].lastMessage});
+ // find the index of active user last message
};
fetchParticipants();
- }, [conversation, currentUser,activeUserId]);
+ }, [conversation, currentUser, activeUserId]);
if (conversation === null) return;
+
if (!currentUser) return null;
+
const handleSelectUser = (userId: string) => {
navigate(`/chat/${userId}`);
};
@@ -60,9 +55,22 @@ export const ChatUserList: React.FC = ({ conversation }) => {
- {participants.length > 0 ? (
- participants.map((user) => {
+ {chatPartners.length > 0 ? (
+ chatPartners.map((user) => {
const isActive = user._id === activeUserId; // highlight current open chat
+ let lastMessage;
+ const lastMessageIndex = conversation.participants.findIndex(
+ (part) => {
+ return part.receiverId === user._id;
+ }
+ );
+ if (lastMessageIndex === -1) {
+ return;
+ } else {
+ lastMessage = {
+ ...conversation.participants[lastMessageIndex].lastMessage,
+ };
+ }
return (
= ({ conversation }) => {
{"..."}
- {lastMessage && (
+ {Object.keys(lastMessage).length !== 0 && (
{formatDistanceToNow(
- new Date(conversation?.lastModified),
+ new Date(lastMessage.time || "Text first"),
{ addSuffix: false }
)}
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 4555c0be1..4d7e1a4eb 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -121,7 +121,6 @@ export const saveMessagesBetweenUsers = async (newMessage: Any) => {
withCredentials: true,
});
console.log("message saved");
-
const { message } = res.data;
return message;
} catch (err) {
@@ -129,16 +128,18 @@ export const saveMessagesBetweenUsers = async (newMessage: Any) => {
}
};
// Helper function to get conversations for a user
-export const getConversationsForUser = async (userId: string | undefined) => {
+export const getConversationsForUser = async (
+ currentUserId: string | undefined,
+ partnerId: string | undefined
+) => {
// Get unique conversation partners
const res = await axios.get(
- `${URL}/conversation/get-conversations-for-user/${userId}`,
+ `${URL}/conversation/get-conversations-for-user?currentUserId=${currentUserId}&partnerId=${partnerId}`,
{
withCredentials: true,
}
);
const { conversation } = res.data;
- console.log(conversation)
return conversation;
};
@@ -152,11 +153,9 @@ export const addConversationsForUser = async (con: object): any[] => {
}
);
const { conversationForSender } = res.data;
- console.log(conversationForSender)
return conversationForSender;
};
-
export const updateConversationsForUser = async (con: object): any[] => {
// Get unique conversation partners
const res = await axios.post(
@@ -167,6 +166,5 @@ export const updateConversationsForUser = async (con: object): any[] => {
}
);
const { conversationForSender } = res.data;
- console.log(conversationForSender)
return conversationForSender;
-};
\ No newline at end of file
+};
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 35d1a8dc3..922923314 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -23,26 +23,27 @@ export const ChatPage: React.FC = () => {
const { user: currentUser } = useAuth();
const [messages, setMessages] = useState
([]);
const [newMessage, setNewMessage] = useState("");
+ const [isTyping, setIsTyping] = useState(false);
+ const [checkStatus, setCheckStatus] = useState(false);
const [conversation, setConversation] = useState();
const messagesEndRef = useRef(null);
const [chatPartner, setChatPartner] = useState(null);
- const [users, setUsers] = useState<[string , User][]>([]);
+ const [users, setUsers] = useState<[string, User][]>([]);
const socket = useRef();
- useEffect(()=>{
- const fetchConversation = async()=>{
- // Load conversations
- const conv = await getConversationsForUser(currentUser?.userId);
- if (conv) setConversation(conv);
- }
+ useEffect(() => {
+ const fetchConversation = async () => {
+ // Load conversations
+ const conv = await getConversationsForUser(currentUser?.userId,userId);
+ if (conv) setConversation(conv);
+ };
fetchConversation();
- },[userId,currentUser?.userId,messages])
+ }, [userId, currentUser?.userId, messages]);
useEffect(() => {
if (!currentUser?.userId || !userId) return; // guard clause
const fetchUserData = async () => {
try {
-
// Load partner Data
const partner = await getUserFromDb(userId);
setChatPartner(partner || null);
@@ -97,11 +98,23 @@ export const ChatPage: React.FC = () => {
setMessages((prev) => [...prev, message]);
});
+ // when user get typing
+ socket.current.on("is-typing", () => {
+ setIsTyping(true);
+ setTimeout(() => {
+ setIsTyping(false);
+ }, 2000);
+ });
+
// when user got hi
socket.current.on("hi", () => {
alert("hi");
});
-
+
+ // when user offline or status changed
+ socket.current.on("check-user-status",()=>{
+ setCheckStatus(!checkStatus);
+ })
return () => {
if (socket.current) {
socket.current.off("connect", handleConnect);
@@ -137,12 +150,11 @@ export const ChatPage: React.FC = () => {
// Update conversation
try {
const con = {
- senderId:currentUser?.userId,
- receiverId:userId,
- lastMessage:{...msg}
- }
+ sender: currentUser?.userId,
+ receiver: userId,
+ lastMessage: { ...msg },
+ };
const updatedConv = await updateConversationsForUser(con);
- console.log(updatedConv);
if (updatedConv) {
setConversation(updatedConv);
}
@@ -179,8 +191,16 @@ export const ChatPage: React.FC = () => {
{chatPartner.name}
-
- {chatPartner.isOnline ? "Online" : "Last seen recently"}
+
+ {isTyping
+ ? "is typing"
+ : chatPartner.isOnline
+ ? "online"
+ : "Last seen recently"}
@@ -261,7 +281,11 @@ export const ChatPage: React.FC = () => {
type="text"
placeholder="Type a message..."
value={newMessage}
- onChange={(e) => setNewMessage(e.target.value)}
+ onChange={(e) => {
+ e.preventDefault();
+ socket.current.emit("typing", chatPartner._id);
+ setNewMessage(e.target.value);
+ }}
fullWidth
className="flex-1"
/>
diff --git a/src/types/index.ts b/src/types/index.ts
index dabae5ede..9e9421b5e 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -38,10 +38,11 @@ export interface Investor extends User {
}
export interface Message {
- senderId: string;
- receiverId: string;
+ sender: string;
+ receiver: string;
content: string;
isRead: boolean;
+ time:Date,
}
export interface ChatConversation {
From 93b52fda984c1cd15015d31583f94bba76d9012d Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Sat, 6 Sep 2025 22:33:09 +0500
Subject: [PATCH 11/49] video call implemented
---
package-lock.json | 8 +-
package.json | 2 +-
src/App.tsx | 198 +++++++++++---------
src/components/layout/Navbar.tsx | 3 +-
src/components/ui/Avatar.tsx | 4 +-
src/components/webRTC/AudioCall.tsx | 88 +++++++++
src/components/webRTC/Videocall.tsx | 189 +++++++++++++++++++
src/components/webrtc/IncomingCallModal.tsx | 36 ++++
src/context/AuthContext.tsx | 9 +-
src/context/SocketContext.tsx | 49 +++++
src/pages/chat/ChatPage.tsx | 111 +++++++----
src/pages/dashboard/InvestorDashboard.tsx | 4 +-
src/pages/messages/MessagesPage.tsx | 6 +-
src/pages/settings/SettingsPage.tsx | 18 +-
src/types/index.ts | 4 +
15 files changed, 580 insertions(+), 149 deletions(-)
create mode 100644 src/components/webRTC/AudioCall.tsx
create mode 100644 src/components/webRTC/Videocall.tsx
create mode 100644 src/components/webrtc/IncomingCallModal.tsx
create mode 100644 src/context/SocketContext.tsx
diff --git a/package-lock.json b/package-lock.json
index 7d980fc21..47d13bbc1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
- "react-hot-toast": "^2.4.1",
+ "react-hot-toast": "^2.6.0",
"react-router-dom": "^6.22.1",
"socket.io-client": "^4.8.1"
},
@@ -3653,9 +3653,9 @@
}
},
"node_modules/react-hot-toast": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz",
- "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
+ "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
diff --git a/package.json b/package.json
index a9be5f4f6..146a53e15 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
- "react-hot-toast": "^2.4.1",
+ "react-hot-toast": "^2.6.0",
"react-router-dom": "^6.22.1",
"socket.io-client": "^4.8.1"
},
diff --git a/src/App.tsx b/src/App.tsx
index c3684194b..88ecbd68e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,106 +1,132 @@
-import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
-import { AuthProvider } from './context/AuthContext';
+import {
+ BrowserRouter as Router,
+ Routes,
+ Route,
+ Navigate,
+} from "react-router-dom";
+import { AuthProvider } from "./context/AuthContext";
+import { SocketProvider } from "./context/SocketContext";
// Layouts
-import { DashboardLayout } from './components/layout/DashboardLayout';
+import { DashboardLayout } from "./components/layout/DashboardLayout";
// Auth Pages
-import { LoginPage } from './pages/auth/LoginPage';
-import { RegisterPage } from './pages/auth/RegisterPage';
+import { LoginPage } from "./pages/auth/LoginPage";
+import { RegisterPage } from "./pages/auth/RegisterPage";
// Dashboard Pages
-import { EntrepreneurDashboard } from './pages/dashboard/EntrepreneurDashboard';
-import { InvestorDashboard } from './pages/dashboard/InvestorDashboard';
+import { EntrepreneurDashboard } from "./pages/dashboard/EntrepreneurDashboard";
+import { InvestorDashboard } from "./pages/dashboard/InvestorDashboard";
// Profile Pages
-import { EntrepreneurProfile } from './pages/profile/EntrepreneurProfile';
-import { InvestorProfile } from './pages/profile/InvestorProfile';
+import { EntrepreneurProfile } from "./pages/profile/EntrepreneurProfile";
+import { InvestorProfile } from "./pages/profile/InvestorProfile";
// Feature Pages
-import { InvestorsPage } from './pages/investors/InvestorsPage';
-import { EntrepreneursPage } from './pages/entrepreneurs/EntrepreneursPage';
-import { MessagesPage } from './pages/messages/MessagesPage';
-import { NotificationsPage } from './pages/notifications/NotificationsPage';
-import { DocumentsPage } from './pages/documents/DocumentsPage';
-import { SettingsPage } from './pages/settings/SettingsPage';
-import { HelpPage } from './pages/help/HelpPage';
-import { DealsPage } from './pages/deals/DealsPage';
+import { InvestorsPage } from "./pages/investors/InvestorsPage";
+import { EntrepreneursPage } from "./pages/entrepreneurs/EntrepreneursPage";
+import { MessagesPage } from "./pages/messages/MessagesPage";
+import { NotificationsPage } from "./pages/notifications/NotificationsPage";
+import { DocumentsPage } from "./pages/documents/DocumentsPage";
+import { SettingsPage } from "./pages/settings/SettingsPage";
+import { HelpPage } from "./pages/help/HelpPage";
+import { DealsPage } from "./pages/deals/DealsPage";
// Chat Pages
-import { ChatPage } from './pages/chat/ChatPage';
-import { ForgotPasswordPage } from './pages/auth/ForgotPasswordPage';
-import { ResetPasswordPage } from './pages/auth/ResetPasswordPage';
+import { ChatPage } from "./pages/chat/ChatPage";
+import { ForgotPasswordPage } from "./pages/auth/ForgotPasswordPage";
+import { ResetPasswordPage } from "./pages/auth/ResetPasswordPage";
+import { VideoCall } from "./components/webRTC/Videocall";
+import { AudioCall } from "./components/webRTC/AudioCall";
+import { Toaster } from "react-hot-toast";
function App() {
return (
+ //
+ //
WebRTC Test
+ //
+ //
-
-
- {/* Authentication Routes */}
- } />
- } />
- } />
- } />
-
- {/* Dashboard Routes */}
- }>
- } />
- } />
-
-
- {/* Profile Routes */}
- }>
- } />
- } />
-
-
- {/* Feature Routes */}
- }>
- } />
-
-
- }>
- } />
-
-
- }>
- } />
-
-
- }>
- } />
-
-
- }>
- } />
-
-
- }>
- } />
-
-
- }>
- } />
-
-
- }>
- } />
-
-
- {/* Chat Routes */}
- }>
- } />
-
-
- {/* Redirect root to login */}
- } />
-
- {/* Catch all other routes and redirect to login */}
- } />
-
-
+
+
+
+ {/* Authentication Routes */}
+ } />
+ } />
+ } />
+ } />
+
+ {/* Dashboard Routes */}
+ }>
+ } />
+ } />
+
+
+ {/* Profile Routes */}
+ }>
+ }
+ />
+ } />
+
+
+ {/* Feature Routes */}
+ }>
+ } />
+
+
+ }>
+ } />
+
+
+ }>
+ } />
+
+
+ }>
+ } />
+
+
+ }>
+ } />
+
+
+ }>
+ } />
+
+
+ }>
+ } />
+
+
+ }>
+
+ }>
+ } />
+
+
+ {/* Chat Routes */}
+ }>
+ } />
+ } />
+ } />
+
+
+ {/* Redirect root to login */}
+ } />
+
+ {/* Catch all other routes and redirect to login */}
+ }
+ />
+
+
+
+
);
}
-export default App;
\ No newline at end of file
+export default App;
diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx
index 511c56842..c6fff0434 100644
--- a/src/components/layout/Navbar.tsx
+++ b/src/components/layout/Navbar.tsx
@@ -7,6 +7,7 @@ import { Button } from '../ui/Button';
export const Navbar: React.FC = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
+
const { user, logout } = useAuth();
const navigate = useNavigate();
@@ -26,7 +27,7 @@ export const Navbar: React.FC = () => {
// User profile route based on role and ID
const profileRoute = user
- ? `/profile/${user.role}/${user.id}`
+ ? `/profile/${user.role}/${user.userId}`
: '/login';
const navLinks = [
diff --git a/src/components/ui/Avatar.tsx b/src/components/ui/Avatar.tsx
index fb0ab2ec7..1c918486a 100644
--- a/src/components/ui/Avatar.tsx
+++ b/src/components/ui/Avatar.tsx
@@ -7,7 +7,7 @@ interface AvatarProps {
alt: string;
size?: AvatarSize;
className?: string;
- status?: 'online' | 'offline' | 'away' | 'busy';
+ status?: "online" | "offline";
}
export const Avatar: React.FC = ({
@@ -28,8 +28,6 @@ export const Avatar: React.FC = ({
const statusColors = {
online: 'bg-success-500',
offline: 'bg-gray-400',
- away: 'bg-warning-500',
- busy: 'bg-error-500',
};
const statusSizes = {
diff --git a/src/components/webRTC/AudioCall.tsx b/src/components/webRTC/AudioCall.tsx
new file mode 100644
index 000000000..df3791f34
--- /dev/null
+++ b/src/components/webRTC/AudioCall.tsx
@@ -0,0 +1,88 @@
+import React, { useEffect, useRef, useState } from "react";
+import { useParams } from "react-router-dom";
+import io from "socket.io-client";
+
+const socket = io("http://localhost:5000");
+
+export const AudioCall = () => {
+ const {roomId} = useParams();
+ const localAudioRef = useRef(null);
+ const remoteAudioRef = useRef(null);
+ const pcRef = useRef(null);
+
+ const [joined, setJoined] = useState(false);
+
+ useEffect(() => {
+ pcRef.current = new RTCPeerConnection();
+
+ // Handle remote stream
+ pcRef.current.ontrack = (event) => {
+ remoteAudioRef.current.srcObject = event.streams[0];
+ };
+
+ // connect the user
+ if(!joined){
+ joinRoom();
+ }
+
+ // Send ICE candidate to peer
+ pcRef.current.onicecandidate = (event) => {
+ if (event.candidate) {
+ socket.emit("ice-candidate", { candidate: event.candidate, roomId });
+ }
+ };
+
+ // Socket listeners
+ socket.on("offer", async ({ offer }) => {
+ await pcRef.current.setRemoteDescription(
+ new RTCSessionDescription(offer)
+ );
+ const answer = await pcRef.current.createAnswer();
+ await pcRef.current.setLocalDescription(answer);
+ socket.emit("answer", { roomId, answer });
+ });
+
+ socket.on("answer", async ({ answer }) => {
+ await pcRef.current.setRemoteDescription(
+ new RTCSessionDescription(answer)
+ );
+ });
+
+ socket.on("ice-candidate", async ({ candidate }) => {
+ try {
+ await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
+ } catch (err) {
+ console.error("Error adding ICE candidate:", err);
+ }
+ });
+ return () => {
+ socket.off("offer");
+ socket.off("answer");
+ socket.off("ice-candidate");
+ };
+ }, [roomId]);
+
+ const joinRoom = async() => {
+ setJoined(true);
+
+ socket.emit("join-room", roomId);
+
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ localAudioRef.current.srcObject = stream;
+
+ stream.getTracks().forEach((track) => {
+ pcRef.current.addTrack(track, stream);
+ });
+
+ const offer = await pcRef.current.createOffer();
+ await pcRef.current.setLocalDescription(offer);
+ socket.emit("offer", { roomId, offer });
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/webRTC/Videocall.tsx b/src/components/webRTC/Videocall.tsx
new file mode 100644
index 000000000..a21e7949f
--- /dev/null
+++ b/src/components/webRTC/Videocall.tsx
@@ -0,0 +1,189 @@
+// client/src/components/VideoCall.js
+import React, { useEffect, useRef, useState } from "react";
+import { useNavigate, useParams } from "react-router-dom";
+import { useAuth } from "../../context/AuthContext";
+import { useSocket } from "../../context/SocketContext";
+import toast from "react-hot-toast";
+
+
+export const VideoCall: React.FC = () => {
+ const { roomId, userId } = useParams();
+
+ const {socket} = useSocket();
+ const { user } = useAuth();
+ const localVideoRef = useRef(null);
+ const remoteVideoRef = useRef(null);
+ const pcRef = useRef(null);
+ const navigate = useNavigate();
+
+ const [joined, setJoined] = useState(false);
+
+ useEffect(() => {
+ pcRef.current = new RTCPeerConnection();
+
+ // Handle remote stream
+ pcRef.current.ontrack = (event) => {
+ if (remoteVideoRef.current) {
+ remoteVideoRef.current.srcObject = event.streams[0];
+ }
+ };
+
+ // Connect user
+ if (!joined) {
+ joinRoom();
+ }
+
+ // Send ICE candidates to peer
+ pcRef.current.onicecandidate = (event) => {
+ if (event.candidate) {
+ socket?.emit("ice-candidate", { roomId, candidate: event.candidate });
+ }
+ };
+
+ // *****Socket listeners*****
+ socket?.on("offer", async ({ offer }) => {
+ if (pcRef.current) {
+ await pcRef.current.setRemoteDescription(
+ new RTCSessionDescription(offer)
+ );
+ const answer = await pcRef.current.createAnswer();
+ await pcRef.current.setLocalDescription(answer);
+ socket?.emit("answer", { roomId, answer });
+ }
+ });
+
+ // Offer Answer
+ socket?.on("answer", async ({ answer }) => {
+ if (pcRef.current) {
+ await pcRef.current.setRemoteDescription(
+ new RTCSessionDescription(answer)
+ );
+ }
+ });
+
+ // receiver ICE
+ socket?.on("ice-candidate", async ({ candidate }) => {
+ try {
+ await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
+ } catch (err) {
+ console.error("Error adding ICE candidate:", err);
+ }
+ });
+
+ // Accepted call
+ socket?.on("call-accepted", () => {
+ toast.success("user joined");
+ });
+
+ // Call ended
+ socket?.on("call-ended", () => {
+ toast.success("call ended");
+ navigate(`/chat/${userId}`);
+ });
+
+// Call rejected
+ socket?.on("call-rejected", () => {
+ console.log("uff");
+ toast.error("Your call was rejected.");
+ navigate(`/chat/${userId}`);
+ });
+
+
+ return () => {
+ socket?.off("offer");
+ socket?.off("call-rejected");
+ socket?.off("call-accepted");
+ socket?.off("answer");
+ socket?.off("ice-candidate");
+ };
+ }, [roomId]);
+
+ const joinRoom = async () => {
+ setJoined(true);
+ socket?.emit("join-room", { roomId });
+
+ try {
+ // Get media
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ });
+ if (localVideoRef.current) {
+ localVideoRef.current.srcObject = stream;
+ }
+
+ // Add tracks to connection
+ stream.getTracks().forEach((track) => {
+ if (pcRef.current) {
+ pcRef.current.addTrack(track, stream);
+ }
+ });
+
+ // Create offer
+ if (pcRef.current) {
+ const offer = await pcRef.current.createOffer();
+ await pcRef.current.setLocalDescription(offer);
+ socket?.emit("offer", { roomId, offer });
+ }
+
+ socket?.emit("start-call", {
+ from: user?.userId,
+ to: userId,
+ roomId: roomId,
+ });
+ } catch (error) {
+ console.log("video call error : " + error);
+ }
+ };
+
+ return (
+
+ {/* Video Area */}
+
+ {/* Local Video (small in corner) */}
+
+
+
+ You
+
+
+
+ {/* Remote Video (big) */}
+
+ {remoteVideoRef.current !== null ? (
+
+ ) : (
+ Waiting for participant...
+ )}
+
+
+
+ {/* Control Bar */}
+
+ {
+ e.preventDefault();
+ socket?.emit("end-call",{roomId});
+ navigate(`/chat/${userId}`);
+ }}>
+ 📞
+
+
+ 🎤
+
+
+ 🎥
+
+
+
+ );
+};
diff --git a/src/components/webrtc/IncomingCallModal.tsx b/src/components/webrtc/IncomingCallModal.tsx
new file mode 100644
index 000000000..788c0037d
--- /dev/null
+++ b/src/components/webrtc/IncomingCallModal.tsx
@@ -0,0 +1,36 @@
+// IncomingCallModal.tsx
+import React from "react";
+
+type Props = {
+ from: string;
+ roomId: string;
+ onAccept: () => void;
+ onReject: () => void;
+};
+
+const IncomingCallModal: React.FC = ({ from, onAccept, onReject }) => {
+ return (
+
+
+
📞 Incoming Call
+
User {from} is calling you
+
+
+ Accept
+
+
+ Reject
+
+
+
+
+ );
+};
+
+export default IncomingCallModal;
diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx
index cb1337fa3..72991a644 100644
--- a/src/context/AuthContext.tsx
+++ b/src/context/AuthContext.tsx
@@ -14,7 +14,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [user, setUser] = useState(null);
- const [userData, setUserData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
// Check for stored user on initial load
@@ -66,8 +65,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
const { token, user } = res.data;
localStorage.setItem("token", token);
- setUser(user);
- setUserData(user);
+ setUser({...user,isOnline:true});
toast.success("Successfully logged in!");
} catch (error) {
toast.error((error as Error).message);
@@ -104,7 +102,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
toast.success("Account created successfully!");
const { token, user } = res.data;
localStorage.setItem("token", token);
- setUserData(user);
setUser(user);
}
} catch (error) {
@@ -185,7 +182,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
// Logout function
const logout = (): void => {
setUser(null);
- setUserData(null);
localStorage.removeItem("token");
toast.success("Logged out successfully");
};
@@ -218,7 +214,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
.then((res) => {
toast.success("profile updated successfully.");
const { user } = res.data;
- setUserData(user);
+ setUser(user);
})
.catch((err) => {
console.log(err);
@@ -227,7 +223,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
const value = {
user,
- userData,
login,
register,
logout,
diff --git a/src/context/SocketContext.tsx b/src/context/SocketContext.tsx
new file mode 100644
index 000000000..ee87d0ff6
--- /dev/null
+++ b/src/context/SocketContext.tsx
@@ -0,0 +1,49 @@
+// SocketContext.tsx
+import React, { createContext, useContext, useEffect, useState } from "react";
+import { io, Socket } from "socket.io-client";
+import { useAuth } from "./AuthContext"; // your existing auth context
+
+type SocketContextType = {
+ socket: Socket | null;
+};
+
+const SocketContext = createContext({ socket: null });
+export const useSocket = () => useContext(SocketContext);
+
+export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
+ const { user } = useAuth(); // logged in user
+ const [socket, setSocket] = useState(null);
+
+ useEffect(() => {
+ if (user) {
+ // connect socket after login
+ const s = io("http://localhost:5000", {
+ withCredentials: true,
+ transports: ["websocket", "polling"],
+ });
+
+ s.on("connect", () => {
+ console.log("Connected to socket:", s.id);
+ // tell backend this userId is online
+ s.emit("join", user.userId);
+ });
+
+ setSocket(s);
+
+ // cleanup on unmount or logout
+ return () => {
+ s.disconnect();
+ setSocket(null);
+ console.log(" Socket disconnected");
+ };
+ }
+ }, [user]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 922923314..cefd46763 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from "react";
-import { useParams } from "react-router-dom";
+import { useNavigate, useParams } from "react-router-dom";
import { Send, Phone, Video, Info, Smile } from "lucide-react";
import { Avatar } from "../../components/ui/Avatar";
import { Button } from "../../components/ui/Button";
@@ -16,7 +16,8 @@ import {
} from "../../data/messages";
import { MessageCircle } from "lucide-react";
import { getUserFromDb } from "../../data/users";
-import { io } from "socket.io-client";
+import { useSocket } from "../../context/SocketContext";
+import IncomingCallModal from "../../components/webrtc/IncomingCallModal";
export const ChatPage: React.FC = () => {
const { userId } = useParams<{ userId: string }>();
@@ -29,17 +30,41 @@ export const ChatPage: React.FC = () => {
const messagesEndRef = useRef(null);
const [chatPartner, setChatPartner] = useState(null);
const [users, setUsers] = useState<[string, User][]>([]);
- const socket = useRef();
+ const { socket } = useSocket();
+ const [incomingCall, setIncomingCall] = useState<{
+ from: string;
+ roomId: string;
+ } | null>(null);
+ const navigate = useNavigate();
+
+ const acceptCall = () => {
+ if (incomingCall) {
+ socket?.emit("accept-call", {
+ from: incomingCall.from,
+ });
+ navigate(`video-call/${incomingCall.roomId}`);
+ setIncomingCall(null);
+ }
+ };
+
+ const rejectCall = () => {
+ if (incomingCall) {
+ socket?.emit("reject-call", { from: incomingCall.from });
+ setIncomingCall(null);
+ }
+ };
+
+ // Load conversations
useEffect(() => {
const fetchConversation = async () => {
- // Load conversations
- const conv = await getConversationsForUser(currentUser?.userId,userId);
+ const conv = await getConversationsForUser(currentUser?.userId, userId);
if (conv) setConversation(conv);
};
fetchConversation();
}, [userId, currentUser?.userId, messages]);
+ // Fetch Partner Data, messages
useEffect(() => {
if (!currentUser?.userId || !userId) return; // guard clause
const fetchUserData = async () => {
@@ -62,6 +87,7 @@ export const ChatPage: React.FC = () => {
fetchUserData();
}, [currentUser?.userId, userId]);
+ // Set Last messages for each conversation
useEffect(() => {
const fetchUsers = async () => {
const uniqueIds = Array.from(new Set(messages.map((m) => m.senderId)));
@@ -80,47 +106,35 @@ export const ChatPage: React.FC = () => {
}
}, [messages]);
- // connect socket.io client
+ // Connect socket.io client
useEffect(() => {
- socket.current = io("http://localhost:5000", {
- withCredentials: true,
+ socket?.on("incoming-call", ({ from, roomId }) => {
+ setIncomingCall({ from, roomId });
});
- const handleConnect = () => {
- socket.current.emit("join", currentUser?.userId);
- };
-
- // connect user
- socket.current.on("connect", handleConnect);
-
// when user receive message
- socket.current.on("received-message", (message) => {
+ socket?.on("received-message", (message) => {
setMessages((prev) => [...prev, message]);
});
// when user get typing
- socket.current.on("is-typing", () => {
+ socket?.on("is-typing", () => {
setIsTyping(true);
setTimeout(() => {
setIsTyping(false);
}, 2000);
});
- // when user got hi
- socket.current.on("hi", () => {
- alert("hi");
- });
-
// when user offline or status changed
- socket.current.on("check-user-status",()=>{
+ socket?.on("check-user-status", () => {
setCheckStatus(!checkStatus);
- })
+ });
return () => {
- if (socket.current) {
- socket.current.off("connect", handleConnect);
- socket.current.off("hi");
- socket.current.disconnect();
- }
+ socket?.off("send-messsage");
+ socket?.off("received-messsage");
+ socket?.off("incoming-call");
+ socket?.off("accept-call");
+ socket?.off("reject-call");
};
}, [currentUser?.userId]);
@@ -143,7 +157,7 @@ export const ChatPage: React.FC = () => {
};
const msg = await saveMessagesBetweenUsers(message);
- socket.current.emit("send-message", msg);
+ socket?.emit("send-message", msg);
setMessages((prev) => [...prev, msg]);
setNewMessage("");
@@ -167,6 +181,19 @@ export const ChatPage: React.FC = () => {
return (
+ {incomingCall && (
+
+ {/* Chat UI */}
+
Chat & Calls
+
+
+
+ )}
{/* Conversations sidebar */}
@@ -193,7 +220,9 @@ export const ChatPage: React.FC = () => {
{isTyping
@@ -211,6 +240,15 @@ export const ChatPage: React.FC = () => {
size="sm"
className="rounded-full p-2"
aria-label="Voice call"
+ onClick={(e) => {
+ e.preventDefault();
+ navigate(
+ `audio-call/${currentUser?.userId.slice(
+ 0,
+ 5
+ )}&${chatPartner?._id.slice(0, 5)}`
+ );
+ }}
>
@@ -220,6 +258,15 @@ export const ChatPage: React.FC = () => {
size="sm"
className="rounded-full p-2"
aria-label="Video call"
+ onClick={(e) => {
+ e.preventDefault();
+ navigate(
+ `video-call/${currentUser?.userId.slice(
+ 0,
+ 5
+ )}&${chatPartner?._id.slice(0, 5)}`
+ );
+ }}
>
@@ -283,7 +330,7 @@ export const ChatPage: React.FC = () => {
value={newMessage}
onChange={(e) => {
e.preventDefault();
- socket.current.emit("typing", chatPartner._id);
+ socket?.emit("typing", chatPartner._id);
setNewMessage(e.target.value);
}}
fullWidth
diff --git a/src/pages/dashboard/InvestorDashboard.tsx b/src/pages/dashboard/InvestorDashboard.tsx
index cb96e5989..98a341dc5 100644
--- a/src/pages/dashboard/InvestorDashboard.tsx
+++ b/src/pages/dashboard/InvestorDashboard.tsx
@@ -14,11 +14,11 @@ export const InvestorDashboard: React.FC = () => {
const { user } = useAuth();
const [searchQuery, setSearchQuery] = useState('');
const [selectedIndustries, setSelectedIndustries] = useState([]);
-
+ console.log(user);
if (!user) return null;
// Get collaboration requests sent by this investor
- const sentRequests = getRequestsFromInvestor(user.id);
+ const sentRequests = getRequestsFromInvestor(user.userId);
// const requestedEntrepreneurIds = sentRequests.map(req => req.entrepreneurId);
const [entrepreneurs,setEnterprenuers] = useState([]);
diff --git a/src/pages/messages/MessagesPage.tsx b/src/pages/messages/MessagesPage.tsx
index 070249a2b..1ee7b2295 100644
--- a/src/pages/messages/MessagesPage.tsx
+++ b/src/pages/messages/MessagesPage.tsx
@@ -1,13 +1,11 @@
import React from 'react';
-import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext';
import { getConversationsForUser } from '../../data/messages';
import { ChatUserList } from '../../components/chat/ChatUserList';
-// import { MessageCircle } from 'lucide-react';
+import { MessageCircle } from 'lucide-react';
export const MessagesPage: React.FC = () => {
const { user } = useAuth();
- const navigate = useNavigate();
if (!user) return null;
@@ -20,7 +18,7 @@ export const MessagesPage: React.FC = () => {
) : (
- {/* */}
+
No messages yet
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index d0391131f..9d11cdf66 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -9,17 +9,17 @@ import { useAuth } from "../../context/AuthContext";
import { Navigate } from "react-router-dom";
export const SettingsPage: React.FC = () => {
- const { user, updateProfile, userData } = useAuth();
+ const { user, updateProfile} = useAuth();
- if (!user || !userData) return null;
+ if (!user ) return null;
const initialValues = {
- name: userData.name,
- email: userData.email,
- role: userData.role,
- bio: userData.bio || "",
- location: userData.location || "",
- avatarUrl: userData.avatarUrl || "",
+ name: user?.name,
+ email: user?.email,
+ role: user?.role,
+ bio: user?.bio || "",
+ location: user?.location || "",
+ avatarUrl: user?.avatarUrl || "",
};
const [userDetails, setUserDetails] = useState(initialValues);
const [isFileUploaded, setIsFileUploaded] = useState(false);
@@ -35,7 +35,7 @@ export const SettingsPage: React.FC = () => {
const handleSubmit = async (e: Event) => {
e.preventDefault();
- updateProfile(userData.userId, userDetails);
+ updateProfile(user?.userId, userDetails);
};
const handleCancel = (e) => {
e.preventDefault();
diff --git a/src/types/index.ts b/src/types/index.ts
index 9e9421b5e..fafe06fc3 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -89,3 +89,7 @@ export interface AuthContextType {
isAuthenticated: boolean;
isLoading: boolean;
}
+
+export interface Socketcontext{
+ socket:string | null;
+}
\ No newline at end of file
From debc0e26dc3ba9dda1691ce00f045bfbbf8e6c17 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Sun, 7 Sep 2025 22:20:05 +0500
Subject: [PATCH 12/49] video call ended except design
---
src/components/webRTC/Videocall.tsx | 203 ++++++++++++++++++----------
src/pages/chat/ChatPage.tsx | 4 +-
2 files changed, 130 insertions(+), 77 deletions(-)
diff --git a/src/components/webRTC/Videocall.tsx b/src/components/webRTC/Videocall.tsx
index a21e7949f..607e74b41 100644
--- a/src/components/webRTC/Videocall.tsx
+++ b/src/components/webRTC/Videocall.tsx
@@ -1,22 +1,21 @@
-// client/src/components/VideoCall.js
import React, { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useAuth } from "../../context/AuthContext";
import { useSocket } from "../../context/SocketContext";
import toast from "react-hot-toast";
-
export const VideoCall: React.FC = () => {
const { roomId, userId } = useParams();
-
- const {socket} = useSocket();
+ const { socket } = useSocket();
const { user } = useAuth();
- const localVideoRef = useRef(null);
- const remoteVideoRef = useRef(null);
- const pcRef = useRef(null);
+
+ const localVideoRef = useRef(null);
+ const remoteVideoRef = useRef(null);
+ const pcRef = useRef(null);
const navigate = useNavigate();
const [joined, setJoined] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
useEffect(() => {
pcRef.current = new RTCPeerConnection();
@@ -28,73 +27,78 @@ export const VideoCall: React.FC = () => {
}
};
- // Connect user
- if (!joined) {
- joinRoom();
- }
-
- // Send ICE candidates to peer
+ // ICE candidates → send to other peer
pcRef.current.onicecandidate = (event) => {
if (event.candidate) {
socket?.emit("ice-candidate", { roomId, candidate: event.candidate });
}
};
- // *****Socket listeners*****
+ // ===== SOCKET LISTENERS =====
socket?.on("offer", async ({ offer }) => {
- if (pcRef.current) {
- await pcRef.current.setRemoteDescription(
+ try {
+ await pcRef.current?.setRemoteDescription(
new RTCSessionDescription(offer)
);
- const answer = await pcRef.current.createAnswer();
- await pcRef.current.setLocalDescription(answer);
- socket?.emit("answer", { roomId, answer });
+ // Create answer
+ const answer = await pcRef.current?.createAnswer();
+ await pcRef.current?.setLocalDescription(answer);
+ socket.emit("answer", { roomId, answer });
+ } catch (err) {
+ console.error("Error handling offer:", err);
}
});
- // Offer Answer
socket?.on("answer", async ({ answer }) => {
- if (pcRef.current) {
- await pcRef.current.setRemoteDescription(
- new RTCSessionDescription(answer)
- );
+ try {
+ if (pcRef.current && !pcRef.current.remoteDescription) {
+ await pcRef.current.setRemoteDescription(
+ new RTCSessionDescription(answer)
+ );
+ }
+ } catch (err) {
+ console.error("Error setting remote description:", err);
}
});
- // receiver ICE
socket?.on("ice-candidate", async ({ candidate }) => {
try {
- await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
+ if (pcRef.current?.remoteDescription) {
+ await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
+ }
} catch (err) {
console.error("Error adding ICE candidate:", err);
}
});
- // Accepted call
socket?.on("call-accepted", () => {
- toast.success("user joined");
+ toast.success("Call accepted");
});
- // Call ended
socket?.on("call-ended", () => {
- toast.success("call ended");
+ toast.success("Call ended");
navigate(`/chat/${userId}`);
});
-// Call rejected
socket?.on("call-rejected", () => {
- console.log("uff");
- toast.error("Your call was rejected.");
+ toast.error("Your call is declined.");
navigate(`/chat/${userId}`);
});
+ socket?.on("receiver-offline", () => {
+ toast.error("The receiver is offline.");
+ navigate(`/chat/${userId}`);
+ });
+
+ if (!joined) joinRoom();
return () => {
socket?.off("offer");
- socket?.off("call-rejected");
- socket?.off("call-accepted");
socket?.off("answer");
socket?.off("ice-candidate");
+ socket?.off("call-accepted");
+ socket?.off("call-rejected");
+ socket?.off("call-ended");
};
}, [roomId]);
@@ -106,32 +110,29 @@ export const VideoCall: React.FC = () => {
// Get media
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
+ audio: true,
});
if (localVideoRef.current) {
localVideoRef.current.srcObject = stream;
}
- // Add tracks to connection
- stream.getTracks().forEach((track) => {
- if (pcRef.current) {
- pcRef.current.addTrack(track, stream);
- }
- });
+ // Add tracks
+ if (pcRef.current?.getSenders().length === 0) {
+ stream
+ .getTracks()
+ .forEach((track) => pcRef.current?.addTrack(track, stream));
+ }
- // Create offer
- if (pcRef.current) {
- const offer = await pcRef.current.createOffer();
- await pcRef.current.setLocalDescription(offer);
+ // Caller creates offer
+ if (user?.userId !== userId) {
+ // Only caller
+ const offer = await pcRef.current?.createOffer();
+ await pcRef.current?.setLocalDescription(offer);
socket?.emit("offer", { roomId, offer });
+ socket?.emit("start-call", { from: user?.userId, to: userId, roomId });
}
-
- socket?.emit("start-call", {
- from: user?.userId,
- to: userId,
- roomId: roomId,
- });
} catch (error) {
- console.log("video call error : " + error);
+ console.log("Video call error:", error);
}
};
@@ -139,49 +140,101 @@ export const VideoCall: React.FC = () => {
{/* Video Area */}
- {/* Local Video (small in corner) */}
-
+ {/* Local Video */}
+
You
- {/* Remote Video (big) */}
+ {/* Remote Video */}
- {remoteVideoRef.current !== null ? (
-
- ) : (
- Waiting for participant...
- )}
+
{/* Control Bar */}
- {
- e.preventDefault();
- socket?.emit("end-call",{roomId});
- navigate(`/chat/${userId}`);
- }}>
+ {
+ e.preventDefault();
+ socket?.emit("end-call", { to: userId, roomId });
+ }}
+ >
📞
-
- 🎤
+ {/* Mic Toggle */}
+ {
+ e.preventDefault();
+ setIsMuted((prev) => {
+ // Toggle mute on local audio tracks
+ const stream = localVideoRef.current?.srcObject as MediaStream;
+ if (stream) {
+ stream.getAudioTracks().forEach((track) => {
+ track.enabled = prev; // if currently muted, enable; else disable
+ });
+ }
+ return !prev;
+ });
+ }}
+ >
+ {isMuted ? "🔇" : "🎤"}
-
- 🎥
+
+ {/* Camera Toggle */}
+ {
+ e.preventDefault();
+
+ const stream = localVideoRef.current?.srcObject as MediaStream;
+ if (!stream || !pcRef.current) return;
+
+ // Get the video track
+ const videoTrack = stream.getVideoTracks()[0];
+ if (!videoTrack) return;
+
+ // Get the sender responsible for this track
+ const sender = pcRef.current
+ .getSenders()
+ .find((s) => s.track && s.track.kind === "video");
+
+ if (sender) {
+ if (videoTrack.enabled) {
+ // Pause sending video
+ videoTrack.enabled = false;
+ console.log(videoTrack)
+ sender.replaceTrack(null); // stop sending to remote
+ } else {
+ // Resume sending video
+ videoTrack.enabled = true;
+ console.log(videoTrack)
+ sender.replaceTrack(videoTrack); // reattach track to remote
+ }
+ }
+ }}
+ >
+ {"🎥"}
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index cefd46763..5f867f73f 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -41,7 +41,7 @@ export const ChatPage: React.FC = () => {
const acceptCall = () => {
if (incomingCall) {
socket?.emit("accept-call", {
- from: incomingCall.from,
+ to: incomingCall.from,
});
navigate(`video-call/${incomingCall.roomId}`);
setIncomingCall(null);
@@ -50,7 +50,7 @@ export const ChatPage: React.FC = () => {
const rejectCall = () => {
if (incomingCall) {
- socket?.emit("reject-call", { from: incomingCall.from });
+ socket?.emit("reject-call", { to: incomingCall.from });
setIncomingCall(null);
}
};
From 8a0335642c11b98cd107a7be77c9dfbb1b0e3f4d Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Mon, 8 Sep 2025 21:49:20 +0500
Subject: [PATCH 13/49] calls added
---
src/components/webRTC/AudioCall.tsx | 237 ++++++++++++++++++++++------
src/components/webRTC/Videocall.tsx | 104 ++++++------
src/pages/chat/ChatPage.tsx | 2 +-
3 files changed, 237 insertions(+), 106 deletions(-)
diff --git a/src/components/webRTC/AudioCall.tsx b/src/components/webRTC/AudioCall.tsx
index df3791f34..0d7eac472 100644
--- a/src/components/webRTC/AudioCall.tsx
+++ b/src/components/webRTC/AudioCall.tsx
@@ -1,88 +1,227 @@
import React, { useEffect, useRef, useState } from "react";
-import { useParams } from "react-router-dom";
-import io from "socket.io-client";
+import { useNavigate, useParams } from "react-router-dom";
+import { useAuth } from "../../context/AuthContext";
+import { useSocket } from "../../context/SocketContext";
+import toast from "react-hot-toast";
-const socket = io("http://localhost:5000");
+export const AudioCall: React.FC = () => {
+ const { roomId, userId } = useParams();
+ const { socket } = useSocket();
+ const { user } = useAuth();
-export const AudioCall = () => {
- const {roomId} = useParams();
- const localAudioRef = useRef(null);
- const remoteAudioRef = useRef(null);
- const pcRef = useRef(null);
+ const localAudioRef = useRef(null);
+ const remoteAudioRef = useRef(null);
+ const pcRef = useRef(null);
+ const navigate = useNavigate();
const [joined, setJoined] = useState(false);
+ const [isMuted, setIsMuted] = useState(false);
useEffect(() => {
pcRef.current = new RTCPeerConnection();
- // Handle remote stream
+ // Handle remote stream
pcRef.current.ontrack = (event) => {
- remoteAudioRef.current.srcObject = event.streams[0];
+ if (remoteAudioRef.current) {
+ remoteAudioRef.current.srcObject = event.streams[0];
+ }
};
- // connect the user
- if(!joined){
- joinRoom();
- }
-
- // Send ICE candidate to peer
+ // ICE candidates → send to other peer
pcRef.current.onicecandidate = (event) => {
if (event.candidate) {
- socket.emit("ice-candidate", { candidate: event.candidate, roomId });
+ socket?.emit("ice-candidate", { roomId, candidate: event.candidate });
+ }
+ };
+
+ const stopStream = () => {
+ const stream = localAudioRef.current?.srcObject as MediaStream;
+ if (stream) {
+ stream.getTracks().forEach((track) => {
+ track.stop(); // stops both mic and camera
+ });
+ if (localAudioRef.current) {
+ localAudioRef.current.srcObject = null;
+ }
}
+
+ // Close peer connection
+ pcRef.current?.close();
+ pcRef.current = null;
+ socket?.emit("end-call", { to: userId, roomId });
};
- // Socket listeners
- socket.on("offer", async ({ offer }) => {
- await pcRef.current.setRemoteDescription(
- new RTCSessionDescription(offer)
- );
- const answer = await pcRef.current.createAnswer();
- await pcRef.current.setLocalDescription(answer);
- socket.emit("answer", { roomId, answer });
+ // ===== SOCKET LISTENERS =====
+ socket?.on("offer", async ({ offer }) => {
+ try {
+ await pcRef.current?.setRemoteDescription(
+ new RTCSessionDescription(offer)
+ );
+ // Create answer
+ const answer = await pcRef.current?.createAnswer();
+ await pcRef.current?.setLocalDescription(answer);
+ socket.emit("answer", { roomId, answer });
+ } catch (err) {
+ console.error("Error handling offer:", err);
+ }
});
- socket.on("answer", async ({ answer }) => {
- await pcRef.current.setRemoteDescription(
- new RTCSessionDescription(answer)
- );
+ socket?.on("answer", async ({ answer }) => {
+ try {
+ if (pcRef.current && !pcRef.current.remoteDescription) {
+ await pcRef.current.setRemoteDescription(
+ new RTCSessionDescription(answer)
+ );
+ }
+ } catch (err) {
+ console.error("Error setting remote description:", err);
+ }
});
- socket.on("ice-candidate", async ({ candidate }) => {
+ socket?.on("ice-candidate", async ({ candidate }) => {
try {
- await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
+ if (pcRef.current?.remoteDescription) {
+ await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
+ }
} catch (err) {
console.error("Error adding ICE candidate:", err);
}
});
+
+ socket?.on("call-accepted", () => {
+ toast.success("Call accepted");
+ });
+
+ socket?.on("call-ended", () => {
+ toast.success("Call ended");
+ navigate(`/chat/${userId}`);
+ });
+
+ socket?.on("call-rejected", () => {
+ toast.error("Your call is declined.");
+ navigate(`/chat/${userId}`);
+ });
+
+ socket?.on("receiver-offline", () => {
+ toast.error("The receiver is offline.");
+ navigate(`/chat/${userId}`);
+ });
+
+ if (!joined) joinRoom();
+
return () => {
- socket.off("offer");
- socket.off("answer");
- socket.off("ice-candidate");
+ socket?.off("offer");
+ socket?.off("answer");
+ socket?.off("ice-candidate");
+ socket?.off("call-accepted");
+ socket?.off("call-rejected");
+ socket?.off("call-ended");
};
}, [roomId]);
- const joinRoom = async() => {
+ const joinRoom = async () => {
setJoined(true);
+ socket?.emit("join-room", { roomId });
+
+ try {
+ // Get media
+ const stream = await navigator.mediaDevices.getUserMedia({
+ audio: true,
+ });
+ if (localAudioRef.current) {
+ localAudioRef.current.srcObject = stream;
+ }
- socket.emit("join-room", roomId);
-
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
- localAudioRef.current.srcObject = stream;
-
- stream.getTracks().forEach((track) => {
- pcRef.current.addTrack(track, stream);
- });
+ // Add tracks
+ if (pcRef.current?.getSenders().length === 0) {
+ stream
+ .getTracks()
+ .forEach((track) => pcRef.current?.addTrack(track, stream));
+ }
- const offer = await pcRef.current.createOffer();
- await pcRef.current.setLocalDescription(offer);
- socket.emit("offer", { roomId, offer });
+ // Caller creates offer
+ if (user?.userId !== userId) {
+ // Only caller
+ const offer = await pcRef.current?.createOffer();
+ await pcRef.current?.setLocalDescription(offer);
+ socket?.emit("offer", { roomId, offer });
+ socket?.emit("start-call", { from: user?.userId, to: userId, roomId });
+ }
+ } catch (error) {
+ console.log("Audio call error:", error);
+ }
};
return (
-
-
-
+
+ {/* Audio Area */}
+
+ {/* Local Audio */}
+
+
+
+ {/* Remote Audio */}
+
+
+
+ {/* Control Bar */}
+
+ {
+ e.preventDefault();
+
+ const stream = localAudioRef.current?.srcObject as MediaStream;
+ if (stream) {
+ // Stop all tracks (camera + mic)
+ stream.getTracks().forEach((track) => {
+ track.stop();
+ });
+
+ // Clear local audio element
+ if (localAudioRef.current) {
+ localAudioRef.current.srcObject = null;
+ }
+ }
+
+ // Close peer connection
+ if (pcRef.current) {
+ pcRef.current
+ .getSenders()
+ .forEach((sender) => sender.track?.stop());
+ pcRef.current.close();
+ pcRef.current = null;
+ }
+
+ // Also clear remote audio element (so frozen audio doesn’t stay visible)
+ if (remoteAudioRef.current) {
+ remoteAudioRef.current.srcObject = null;
+ }
+
+ // Tell server call ended
+ socket?.emit("end-call", { to: userId, roomId });
+ navigate(`/chat/${userId}`);
+ }}
+ >
+ 📞
+
+
);
};
diff --git a/src/components/webRTC/Videocall.tsx b/src/components/webRTC/Videocall.tsx
index 607e74b41..e66d086f8 100644
--- a/src/components/webRTC/Videocall.tsx
+++ b/src/components/webRTC/Videocall.tsx
@@ -34,6 +34,23 @@ export const VideoCall: React.FC = () => {
}
};
+ const stopStream = () => {
+ const stream = localVideoRef.current?.srcObject as MediaStream;
+ if (stream) {
+ stream.getTracks().forEach((track) => {
+ track.stop(); // stops both mic and camera
+ });
+ if (localVideoRef.current) {
+ localVideoRef.current.srcObject = null;
+ }
+ }
+
+ // Close peer connection
+ pcRef.current?.close();
+ pcRef.current = null;
+ socket?.emit("end-call", { to: userId, roomId });
+ };
+
// ===== SOCKET LISTENERS =====
socket?.on("offer", async ({ offer }) => {
try {
@@ -150,7 +167,7 @@ export const VideoCall: React.FC = () => {
ref={localVideoRef}
autoPlay
playsInline
- muted
+ // muted
className={`w-full h-full object-cover`}
/>
@@ -164,7 +181,7 @@ export const VideoCall: React.FC = () => {
ref={remoteVideoRef}
autoPlay
playsInline
- muted
+ // muted
className="w-full h-full object-cover"
/>
@@ -174,67 +191,42 @@ export const VideoCall: React.FC = () => {
{
- e.preventDefault();
- socket?.emit("end-call", { to: userId, roomId });
- }}
- >
- 📞
-
- {/* Mic Toggle */}
- {
- e.preventDefault();
- setIsMuted((prev) => {
- // Toggle mute on local audio tracks
- const stream = localVideoRef.current?.srcObject as MediaStream;
- if (stream) {
- stream.getAudioTracks().forEach((track) => {
- track.enabled = prev; // if currently muted, enable; else disable
- });
- }
- return !prev;
- });
- }}
- >
- {isMuted ? "🔇" : "🎤"}
-
-
- {/* Camera Toggle */}
- {
+ onClick={async (e) => {
e.preventDefault();
const stream = localVideoRef.current?.srcObject as MediaStream;
- if (!stream || !pcRef.current) return;
-
- // Get the video track
- const videoTrack = stream.getVideoTracks()[0];
- if (!videoTrack) return;
-
- // Get the sender responsible for this track
- const sender = pcRef.current
- .getSenders()
- .find((s) => s.track && s.track.kind === "video");
-
- if (sender) {
- if (videoTrack.enabled) {
- // Pause sending video
- videoTrack.enabled = false;
- console.log(videoTrack)
- sender.replaceTrack(null); // stop sending to remote
- } else {
- // Resume sending video
- videoTrack.enabled = true;
- console.log(videoTrack)
- sender.replaceTrack(videoTrack); // reattach track to remote
+ if (stream) {
+ // Stop all tracks (camera + mic)
+ stream.getTracks().forEach((track) => {
+ track.stop();
+ });
+
+ // Clear local video element
+ if (localVideoRef.current) {
+ localVideoRef.current.srcObject = null;
}
}
+
+ // Close peer connection
+ if (pcRef.current) {
+ pcRef.current
+ .getSenders()
+ .forEach((sender) => sender.track?.stop());
+ pcRef.current.close();
+ pcRef.current = null;
+ }
+
+ // Also clear remote video element (so frozen video doesn’t stay visible)
+ if (remoteVideoRef.current) {
+ remoteVideoRef.current.srcObject = null;
+ }
+
+ // Tell server call ended
+ socket?.emit("end-call", { to: userId, roomId });
+ navigate(`/chat/${userId}`);
}}
>
- {"🎥"}
+ 📞
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 5f867f73f..411afd24d 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -43,7 +43,7 @@ export const ChatPage: React.FC = () => {
socket?.emit("accept-call", {
to: incomingCall.from,
});
- navigate(`video-call/${incomingCall.roomId}`);
+ navigate(`audio-call/${incomingCall.roomId}`);
setIncomingCall(null);
}
};
From eb81a752433b39ad2f366b5e3bd054d53ae5cbcd Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Mon, 8 Sep 2025 22:13:35 +0500
Subject: [PATCH 14/49] incoming modal over app
---
src/App.tsx | 8 +-
src/components/layout/DashboardLayout.tsx | 96 ++++++++++++++-----
src/pages/chat/ChatPage.tsx | 62 +++++-------
.../webRTC/AudioCall.tsx | 0
.../webRTC/Videocall.tsx | 0
5 files changed, 97 insertions(+), 69 deletions(-)
rename src/{components => pages}/webRTC/AudioCall.tsx (100%)
rename src/{components => pages}/webRTC/Videocall.tsx (100%)
diff --git a/src/App.tsx b/src/App.tsx
index 88ecbd68e..60cc78901 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -36,16 +36,12 @@ import { DealsPage } from "./pages/deals/DealsPage";
import { ChatPage } from "./pages/chat/ChatPage";
import { ForgotPasswordPage } from "./pages/auth/ForgotPasswordPage";
import { ResetPasswordPage } from "./pages/auth/ResetPasswordPage";
-import { VideoCall } from "./components/webRTC/Videocall";
-import { AudioCall } from "./components/webRTC/AudioCall";
+import { VideoCall } from "./pages/webRTC/Videocall";
+import { AudioCall } from "./pages/webRTC/AudioCall";
import { Toaster } from "react-hot-toast";
function App() {
return (
- //
- //
WebRTC Test
- //
- //
diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx
index be574357b..201d5d6f3 100644
--- a/src/components/layout/DashboardLayout.tsx
+++ b/src/components/layout/DashboardLayout.tsx
@@ -1,36 +1,80 @@
-import React from "react";
-import { Outlet, Navigate } from "react-router-dom";
+import React, { useEffect, useState } from "react";
+import { Outlet, Navigate, useNavigate } from "react-router-dom";
import { useAuth } from "../../context/AuthContext";
import { Navbar } from "./Navbar";
import { Sidebar } from "./Sidebar";
+import { useSocket } from "../../context/SocketContext";
+import IncomingCallModal from "../webrtc/IncomingCallModal";
export const DashboardLayout: React.FC = () => {
const { isAuthenticated, isLoading } = useAuth();
- if (isLoading) {
- return (
-
- );
- }
- if (!isAuthenticated) {
- return ;
- }
-
-
+ const { socket } = useSocket();
+ const navigate = useNavigate();
+
+ const [incomingCall, setIncomingCall] = useState<{
+ from: string;
+ roomId: string;
+ } | null>(null);
+
+ if (isLoading) {
return (
-
-
-
-
+
);
+ }
+ if (!isAuthenticated) {
+ return
;
+ }
+
+ const acceptCall = () => {
+ if (incomingCall) {
+ socket?.emit("accept-call", { to: incomingCall.from });
+ navigate(`/chat/${incomingCall.from}/audio-call/${incomingCall.roomId}`);
+ setIncomingCall(null);
+ }
+ };
+
+ const rejectCall = () => {
+ if (incomingCall) {
+ socket?.emit("reject-call", { to: incomingCall.from });
+ setIncomingCall(null);
+ }
+ };
+
+ useEffect(() => {
+ const handleIncoming = ({ from, roomId }: { from: string; roomId: string }) => {
+ setIncomingCall({ from, roomId });
+ };
+
+ socket?.on("incoming-call", handleIncoming);
+ return () => {
+ socket?.off("incoming-call", handleIncoming);
+ };
+ }, [socket]);
+
+ return (
+
+
+ {incomingCall && (
+
+ )}
+
+
+
+
+
+ );
};
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 411afd24d..529802d12 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -32,28 +32,28 @@ export const ChatPage: React.FC = () => {
const [users, setUsers] = useState<[string, User][]>([]);
const { socket } = useSocket();
- const [incomingCall, setIncomingCall] = useState<{
- from: string;
- roomId: string;
- } | null>(null);
+ // const [incomingCall, setIncomingCall] = useState<{
+ // from: string;
+ // roomId: string;
+ // } | null>(null);
const navigate = useNavigate();
- const acceptCall = () => {
- if (incomingCall) {
- socket?.emit("accept-call", {
- to: incomingCall.from,
- });
- navigate(`audio-call/${incomingCall.roomId}`);
- setIncomingCall(null);
- }
- };
-
- const rejectCall = () => {
- if (incomingCall) {
- socket?.emit("reject-call", { to: incomingCall.from });
- setIncomingCall(null);
- }
- };
+ // const acceptCall = () => {
+ // if (incomingCall) {
+ // socket?.emit("accept-call", {
+ // to: incomingCall.from,
+ // });
+ // navigate(`audio-call/${incomingCall.roomId}`);
+ // setIncomingCall(null);
+ // }
+ // };
+
+ // const rejectCall = () => {
+ // if (incomingCall) {
+ // socket?.emit("reject-call", { to: incomingCall.from });
+ // setIncomingCall(null);
+ // }
+ // };
// Load conversations
useEffect(() => {
@@ -108,9 +108,9 @@ export const ChatPage: React.FC = () => {
// Connect socket.io client
useEffect(() => {
- socket?.on("incoming-call", ({ from, roomId }) => {
- setIncomingCall({ from, roomId });
- });
+ // socket?.on("incoming-call", ({ from, roomId }) => {
+ // setIncomingCall({ from, roomId });
+ // });
// when user receive message
socket?.on("received-message", (message) => {
@@ -132,7 +132,7 @@ export const ChatPage: React.FC = () => {
return () => {
socket?.off("send-messsage");
socket?.off("received-messsage");
- socket?.off("incoming-call");
+ // socket?.off("incoming-call");
socket?.off("accept-call");
socket?.off("reject-call");
};
@@ -181,19 +181,7 @@ export const ChatPage: React.FC = () => {
return (
- {incomingCall && (
-
- {/* Chat UI */}
-
Chat & Calls
-
-
-
- )}
+
{/* Conversations sidebar */}
diff --git a/src/components/webRTC/AudioCall.tsx b/src/pages/webRTC/AudioCall.tsx
similarity index 100%
rename from src/components/webRTC/AudioCall.tsx
rename to src/pages/webRTC/AudioCall.tsx
diff --git a/src/components/webRTC/Videocall.tsx b/src/pages/webRTC/Videocall.tsx
similarity index 100%
rename from src/components/webRTC/Videocall.tsx
rename to src/pages/webRTC/Videocall.tsx
From c7169062ccfe02c49099eaae9d3787f71014fe93 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 9 Sep 2025 16:43:30 +0500
Subject: [PATCH 15/49] collaboration requests added
---
.../CollaborationRequestCard.tsx | 35 ++--
src/data/collaborationRequests.ts | 175 +++++++++++-------
src/pages/dashboard/EntrepreneurDashboard.tsx | 46 +++--
src/pages/dashboard/InvestorDashboard.tsx | 164 +++++++++-------
src/pages/profile/EntrepreneurProfile.tsx | 152 +++++++--------
src/types/index.ts | 9 +-
6 files changed, 347 insertions(+), 234 deletions(-)
diff --git a/src/components/collaboration/CollaborationRequestCard.tsx b/src/components/collaboration/CollaborationRequestCard.tsx
index 7a47eca70..32c0b6176 100644
--- a/src/components/collaboration/CollaborationRequestCard.tsx
+++ b/src/components/collaboration/CollaborationRequestCard.tsx
@@ -1,13 +1,14 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Check, X, MessageCircle } from 'lucide-react';
-import { CollaborationRequest } from '../../types';
+import { CollaborationRequest, Investor } from '../../types';
import { Card, CardBody, CardFooter } from '../ui/Card';
import { Avatar } from '../ui/Avatar';
import { Badge } from '../ui/Badge';
import { Button } from '../ui/Button';
import { updateRequestStatus } from '../../data/collaborationRequests';
import { formatDistanceToNow } from 'date-fns';
+import { getInvestorById } from '../../data/users';
interface CollaborationRequestCardProps {
request: CollaborationRequest;
@@ -19,34 +20,42 @@ export const CollaborationRequestCard: React.FC =
onStatusUpdate
}) => {
const navigate = useNavigate();
- const investor = findUserById(request.investorId);
+ const [investor, setInvestor] = useState();
+ useEffect(() => {
+ const fetchInvestors = async () => {
+ const investor = await getInvestorById(request.inves_id);
+ setInvestor(investor);
+ };
+ fetchInvestors();
+ }, []);
+
if (!investor) return null;
- const handleAccept = () => {
- updateRequestStatus(request.id, 'accepted');
+const handleAccept = () => {
+ updateRequestStatus(request._id, 'accepted');
if (onStatusUpdate) {
- onStatusUpdate(request.id, 'accepted');
+ onStatusUpdate(request._id, 'accepted');
}
};
const handleReject = () => {
- updateRequestStatus(request.id, 'rejected');
+ updateRequestStatus(request._id, 'rejected');
if (onStatusUpdate) {
- onStatusUpdate(request.id, 'rejected');
+ onStatusUpdate(request._id, 'rejected');
}
};
const handleMessage = () => {
- navigate(`/chat/${investor.id}`);
+ navigate(`/chat/${investor.userId}`);
};
const handleViewProfile = () => {
- navigate(`/profile/investor/${investor.id}`);
+ navigate(`/profile/investor/${investor.userId}`);
};
const getStatusBadge = () => {
- switch (request.status) {
+ switch (request.requestStatus) {
case 'pending':
return Pending ;
case 'accepted':
@@ -74,7 +83,7 @@ export const CollaborationRequestCard: React.FC =
{investor.name}
- {formatDistanceToNow(new Date(request.createdAt), { addSuffix: true })}
+ {formatDistanceToNow(new Date(request.time), { addSuffix: true })}
@@ -88,7 +97,7 @@ export const CollaborationRequestCard: React.FC
=
- {request.status === 'pending' ? (
+ {request.requestStatus === 'pending' ? (
{
- return collaborationRequests
- .filter(request => request.entrepreneurId === entrepreneurId)
- .sort((a,b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
+export const getRequestsForEntrepreneur = async (
+ enter_id: string
+): CollaborationRequest[] => {
+ const res = await axios.get(
+ URL + "/requests/get-request-for-enterpreneur/" + enter_id,
+ {
+ withCredentials: true,
+ }
+ );
+ const { requests } = res.data;
+ return requests;
};
// Helper function to get collaboration requests sent by an investor
-export const getRequestsFromInvestor = (investorId: string): CollaborationRequest[] => {
- return collaborationRequests
- .filter(request => request.investorId === investorId)
- .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
+export const getRequestsFromInvestor = async (
+ inves_id: string
+): CollaborationRequest[] => {
+ const res = await axios.post(
+ URL + "/requests/get-request-for-investor",
+ { inves_id },
+ {
+ withCredentials: true,
+ }
+ );
+ const { requests } = res.data;
+ return requests;
+};
+export const checkRequestsFromInvestor = async (
+ inves_id: string,
+ enter_id: string
+): Promise => {
+ try {
+ const body = { inves_id, enter_id };
+ const res = await axios.post(
+ URL + "/requests/check-request-for-investor",
+ body,
+ { withCredentials: true }
+ );
+
+ const { request } = res.data;
+ return !!request; // true if found, false otherwise
+ } catch (error) {
+ console.error("checkRequestsFromInvestor error:", error);
+ return false;
+ }
};
// Helper function to update a collaboration request status
-export const updateRequestStatus = (requestId: string, newStatus: 'pending' | 'accepted' | 'rejected'): CollaborationRequest | null => {
- const requestIndex = collaborationRequests.findIndex(req => req.id === requestId);
- if (requestIndex === -1) return null;
-
- collaborationRequests[requestIndex] = {
- ...collaborationRequests[requestIndex],
- status: newStatus
- };
-
- return collaborationRequests[requestIndex];
+export const updateRequestStatus = async (
+ requestId: string,
+ newStatus: "pending" | "accepted" | "rejected"
+): CollaborationRequest => {
+ const res = await axios.put(
+ URL + "/requests/update-status",
+ { requestId, newStatus },
+ { withCredentials: true }
+ );
+ const {request} = res.data;
+ toast.success("request status updated");
+ return request;
};
// Helper function to create a new collaboration request
-export const createCollaborationRequest = (
- investorId: string,
- entrepreneurId: string,
+export const createCollaborationRequest = async (
+ inves_id: string,
+ enter_id: string,
message: string
): CollaborationRequest => {
- const newRequest: CollaborationRequest = {
- id: `req${collaborationRequests.length + 1}`,
- investorId,
- entrepreneurId,
+ const newRequest = {
+ inves_id,
+ enter_id,
message,
- status: 'pending',
- createdAt: new Date().toISOString()
+ requestStatus: "pending",
};
-
- collaborationRequests.push(newRequest);
- return newRequest;
-};
\ No newline at end of file
+ try {
+ const res = await axios.post(URL + "/requests/save-request", newRequest, {
+ withCredentials: true,
+ });
+ toast.success("Request sent..");
+ return res.data.request as CollaborationRequest;
+ } catch (error) {
+ console.log(error);
+ throw error;
+ }
+};
diff --git a/src/pages/dashboard/EntrepreneurDashboard.tsx b/src/pages/dashboard/EntrepreneurDashboard.tsx
index 989dd1a52..1672314b5 100644
--- a/src/pages/dashboard/EntrepreneurDashboard.tsx
+++ b/src/pages/dashboard/EntrepreneurDashboard.tsx
@@ -15,7 +15,7 @@ import { CollaborationRequestCard } from "../../components/collaboration/Collabo
import { InvestorCard } from "../../components/investor/InvestorCard";
import { useAuth } from "../../context/AuthContext";
import { CollaborationRequest } from "../../types";
-import { getRequestsForEntrepreneur } from "../../data/collaborationRequests";
+import { getRequestsForEntrepreneur, updateRequestStatus } from "../../data/collaborationRequests";
import { getInvestorsFromDb } from "../../data/users";
export const EntrepreneurDashboard: React.FC = () => {
@@ -31,31 +31,51 @@ export const EntrepreneurDashboard: React.FC = () => {
const investors = await getInvestorsFromDb();
console.log(investors);
setRecommendedInvestors(investors);
+ }
+ };
+ fetchData();
+ }, []);
- const requests = getRequestsForEntrepreneur(user.id);
- setCollaborationRequests(requests);
+ useEffect(() => {
+ const fetchData = async () => {
+ if (user) {
+ const investors = await getInvestorsFromDb();
+ console.log(investors);
+ setRecommendedInvestors(investors);
}
};
fetchData();
}, []);
+ if (!user) return null;
+
+ useEffect(() => {
+ const fetchRequests = async () => {
+ const requests = await getRequestsForEntrepreneur(user?.userId);
+ setCollaborationRequests(Array.isArray(requests) ? requests : []);
+ };
+ fetchRequests();
+ }, [user?.userId]);
+
+ const pendingRequests =
+ collaborationRequests.length > 0 &&
+ Array.isArray(collaborationRequests) &&
+ collaborationRequests.length > 0
+ ? collaborationRequests.filter((req) => req.requestStatus === "pending")
+ : [];
+
const handleRequestStatusUpdate = (
requestId: string,
status: "accepted" | "rejected"
) => {
setCollaborationRequests((prevRequests) =>
prevRequests.map((req) =>
- req.id === requestId ? { ...req, status } : req
+ req._id === requestId ? { ...req, requestStatus:status } : req
)
);
+ updateRequestStatus(requestId,status)
};
- if (!user) return null;
-
- const pendingRequests = collaborationRequests.filter(
- (req) => req.status === "pending"
- );
-
return (
@@ -106,7 +126,7 @@ export const EntrepreneurDashboard: React.FC = () => {
{
collaborationRequests.filter(
- (req) => req.status === "accepted"
+ (req) => req.requestStatus === "accepted"
).length
}
@@ -164,7 +184,7 @@ export const EntrepreneurDashboard: React.FC = () => {
{collaborationRequests.map((request) => (
@@ -205,7 +225,7 @@ export const EntrepreneurDashboard: React.FC = () => {
{recommendedInvestors && recommendedInvestors.length > 0 ? (
recommendedInvestors.map((investor, i) => (
-
+
))
) : (
diff --git a/src/pages/dashboard/InvestorDashboard.tsx b/src/pages/dashboard/InvestorDashboard.tsx
index 98a341dc5..888c51ea1 100644
--- a/src/pages/dashboard/InvestorDashboard.tsx
+++ b/src/pages/dashboard/InvestorDashboard.tsx
@@ -1,86 +1,97 @@
-import React, { useEffect, useState } from 'react';
-import { Link } from 'react-router-dom';
-import { Users, PieChart, Filter, Search, PlusCircle } from 'lucide-react';
-import { Button } from '../../components/ui/Button';
-import { Card, CardBody, CardHeader } from '../../components/ui/Card';
-import { Input } from '../../components/ui/Input';
-import { Badge } from '../../components/ui/Badge';
-import { EntrepreneurCard } from '../../components/entrepreneur/EntrepreneurCard';
-import { useAuth } from '../../context/AuthContext';
-import { getRequestsFromInvestor } from '../../data/collaborationRequests';
-import { getEnterprenuerFromDb } from '../../data/users';
+import React, { useEffect, useState } from "react";
+import { Link } from "react-router-dom";
+import { Users, PieChart, Filter, Search, PlusCircle } from "lucide-react";
+import { Button } from "../../components/ui/Button";
+import { Card, CardBody, CardHeader } from "../../components/ui/Card";
+import { Input } from "../../components/ui/Input";
+import { Badge } from "../../components/ui/Badge";
+import { EntrepreneurCard } from "../../components/entrepreneur/EntrepreneurCard";
+import { useAuth } from "../../context/AuthContext";
+import { getRequestsFromInvestor } from "../../data/collaborationRequests";
+import { getEnterprenuerFromDb } from "../../data/users";
+import { CollaborationRequest } from "../../types";
export const InvestorDashboard: React.FC = () => {
const { user } = useAuth();
- const [searchQuery, setSearchQuery] = useState('');
+ const [searchQuery, setSearchQuery] = useState("");
const [selectedIndustries, setSelectedIndustries] = useState
([]);
console.log(user);
if (!user) return null;
-
+
// Get collaboration requests sent by this investor
- const sentRequests = getRequestsFromInvestor(user.userId);
+
// const requestedEntrepreneurIds = sentRequests.map(req => req.entrepreneurId);
- const [entrepreneurs,setEnterprenuers] = useState([]);
+ const [entrepreneurs, setEnterprenuers] = useState([]);
+ const [sentRequests, setSentRequests] = useState([]);
const industries = [];
- useEffect(() => {
- const fetchData = async()=>{
+
+ useEffect(() => {
+ const fetchData = async () => {
if (user) {
const entrepreneurs = await getEnterprenuerFromDb();
setEnterprenuers(entrepreneurs);
- entrepreneurs.map(e=>{
+ entrepreneurs.map((e) => {
industries.push(e.industry);
});
- console.log(industries);
}
- }
- fetchData();
- }, []);
+ };
+ fetchData();
+ }, []);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const requests = await getRequestsFromInvestor(user.userId);
+ setSentRequests(requests);
+ };
+ fetchData();
+ }, [user.userId]);
+
// Filter entrepreneurs based on search and industry filters
const filteredEntrepreneurs = entrepreneurs;
// const filteredEntrepreneurs = entrepreneurs.filter(entrepreneur => {
// // Search filter
- // const matchesSearch = searchQuery === '' ||
+ // const matchesSearch = searchQuery === '' ||
// entrepreneur.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
// entrepreneur.startupName.toLowerCase().includes(searchQuery.toLowerCase()) ||
// entrepreneur.industry.toLowerCase().includes(searchQuery.toLowerCase()) ||
// entrepreneur.pitchSummary.toLowerCase().includes(searchQuery.toLowerCase());
-
+
// // Industry filter
- // const matchesIndustry = selectedIndustries.length === 0 ||
+ // const matchesIndustry = selectedIndustries.length === 0 ||
// selectedIndustries.includes(entrepreneur.industry);
-
+
// return matchesSearch && matchesIndustry;
// });
-
+
// Get unique industries for filter
-
+
// Toggle industry selection
const toggleIndustry = (industry: string) => {
- setSelectedIndustries(prevSelected =>
+ setSelectedIndustries((prevSelected) =>
prevSelected.includes(industry)
- ? prevSelected.filter(i => i !== industry)
+ ? prevSelected.filter((i) => i !== industry)
: [...prevSelected, industry]
);
};
-
+
return (
-
Discover Startups
-
Find and connect with promising entrepreneurs
+
+ Discover Startups
+
+
+ Find and connect with promising entrepreneurs
+
-
+
-
}
- >
- View All Startups
-
+
}>View All Startups
-
+
{/* Filters and search */}
@@ -92,17 +103,21 @@ export const InvestorDashboard: React.FC = () => {
startAdornment={ }
/>
-
+
-
Filter by:
-
+
+ Filter by:
+
+
- {industries.map(industry => (
+ {industries.map((industry) => (
toggleIndustry(industry)}
>
@@ -113,7 +128,7 @@ export const InvestorDashboard: React.FC = () => {
-
+
{/* Stats summary */}
@@ -123,13 +138,17 @@ export const InvestorDashboard: React.FC = () => {
-
Total Startups
-
{entrepreneurs && entrepreneurs.length}
+
+ Total Startups
+
+
+ {entrepreneurs && entrepreneurs.length}
+
-
+
@@ -137,13 +156,17 @@ export const InvestorDashboard: React.FC = () => {
-
Industries
-
{industries.length}
+
+ Industries
+
+
+ {industries.length}
+
-
+
@@ -151,41 +174,48 @@ export const InvestorDashboard: React.FC = () => {
-
Your Connections
+
+ Your Connections
+
- {sentRequests.filter(req => req.status === 'accepted').length}
+ {sentRequests &&
+ sentRequests.filter(
+ (req) => req.requestStatus === "accepted"
+ ).length}
-
+
{/* Entrepreneurs grid */}
- Featured Startups
+
+ Featured Startups
+
-
+
{entrepreneurs && entrepreneurs.length > 0 ? (
-
- {entrepreneurs && entrepreneurs.map(entrepreneur => (
-
-
-
- ))}
+ {entrepreneurs &&
+ entrepreneurs.map((entrepreneur) => (
+
+
+
+ ))}
) : (
No startups match your filters
-
{
- setSearchQuery('');
+ setSearchQuery("");
setSelectedIndustries([]);
}}
>
@@ -198,4 +228,4 @@ export const InvestorDashboard: React.FC = () => {
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/profile/EntrepreneurProfile.tsx b/src/pages/profile/EntrepreneurProfile.tsx
index b354735fb..dfd0229dc 100644
--- a/src/pages/profile/EntrepreneurProfile.tsx
+++ b/src/pages/profile/EntrepreneurProfile.tsx
@@ -17,8 +17,8 @@ import { Card, CardBody, CardHeader } from "../../components/ui/Card";
import { Badge } from "../../components/ui/Badge";
import { useAuth } from "../../context/AuthContext";
import {
+ checkRequestsFromInvestor,
createCollaborationRequest,
- getRequestsFromInvestor,
} from "../../data/collaborationRequests";
import { getEnterpreneurById, updateEntrepreneurData } from "../../data/users";
import { Entrepreneur } from "../../types";
@@ -29,6 +29,8 @@ export const EntrepreneurProfile: React.FC = () => {
const { user: currentUser } = useAuth();
const [isEditing, setIsEditing] = useState(false);
const [entrepreneur, setEnterpreneur] = useState
();
+ const [hasRequestedCollaboration, setHasRequestedCollaboration] =
+ useState();
const initialData = {
userId: id,
startupName: entrepreneur?.startupName,
@@ -50,9 +52,18 @@ export const EntrepreneurProfile: React.FC = () => {
const entrepreneur = await getEnterpreneurById(id);
setEnterpreneur(entrepreneur);
};
+
fetchEntrepreneur();
}, []);
+ useEffect(() => {
+ const checkInvestor = async()=>{
+ const request = await checkRequestsFromInvestor(currentUser?.userId,id);
+ setHasRequestedCollaboration(request);
+ }
+ checkInvestor();
+ }, [currentUser?.userId,id]);
+
useEffect(() => {
setFormData(initialData);
}, [entrepreneur]);
@@ -82,20 +93,15 @@ export const EntrepreneurProfile: React.FC = () => {
const isInvestor = currentUser?.role === "investor";
// Check if the current investor has already sent a request to this entrepreneur
- const hasRequestedCollaboration =
- isInvestor && id
- ? getRequestsFromInvestor(currentUser.userId).some(
- (req) => req.entrepreneurId === id
- )
- : false;
-
- const handleSendRequest = () => {
+
+ const handleSendRequest = async () => {
if (isInvestor && currentUser && id) {
createCollaborationRequest(
currentUser.userId,
id,
`I'm interested in learning more about ${entrepreneur.startupName} and would like to explore potential investment opportunities.`
);
+ await setHasRequestedCollaboration(true);
}
};
@@ -149,71 +155,71 @@ export const EntrepreneurProfile: React.FC = () => {
className="gap-5 flex flex-col text-sm justify-center items-center"
>
-
Date: Tue, 9 Sep 2025 18:26:19 +0500
Subject: [PATCH 16/49] URL changings
---
.env | 1 +
package-lock.json | 18 ++++++++++++++++++
package.json | 1 +
src/data/collaborationRequests.ts | 2 +-
src/data/messages.ts | 2 +-
src/data/users.ts | 2 +-
6 files changed, 23 insertions(+), 3 deletions(-)
create mode 100644 .env
diff --git a/.env b/.env
new file mode 100644
index 000000000..9ce68e432
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+BACKEND_URL="http://nexus-backend-production-02aa.up.railway.app"
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 47d13bbc1..d5ae710ea 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -21,6 +21,7 @@
},
"devDependencies": {
"@eslint/js": "^9.9.1",
+ "@types/node": "^24.3.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
@@ -1278,6 +1279,16 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
+ "node_modules/@types/node": {
+ "version": "24.3.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
+ "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.10.0"
+ }
+ },
"node_modules/@types/prop-types": {
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
@@ -4229,6 +4240,13 @@
}
}
},
+ "node_modules/undici-types": {
+ "version": "7.10.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
+ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
diff --git a/package.json b/package.json
index 146a53e15..b50efb5f1 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
},
"devDependencies": {
"@eslint/js": "^9.9.1",
+ "@types/node": "^24.3.1",
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
diff --git a/src/data/collaborationRequests.ts b/src/data/collaborationRequests.ts
index 3ab8d9c76..e1d6cff82 100644
--- a/src/data/collaborationRequests.ts
+++ b/src/data/collaborationRequests.ts
@@ -50,7 +50,7 @@ export const collaborationRequests: CollaborationRequest[] = [
},
];
-const URL = "http://localhost:5000";
+const URL = process.env.BACKEND_URL;
// Helper function to get collaboration requests for an entrepreneur
export const getRequestsForEntrepreneur = async (
enter_id: string
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 4d7e1a4eb..8922dcf4b 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -99,7 +99,7 @@ export const messages: Message[] = [
},
];
-const URL = "http://localhost:5000";
+const URL = process.env.BACKEND_URL;
// Helper function to get messages between two users
export const getMessagesBetweenUsers = async (
user1Id: string,
diff --git a/src/data/users.ts b/src/data/users.ts
index c96322810..c93de9029 100644
--- a/src/data/users.ts
+++ b/src/data/users.ts
@@ -1,6 +1,6 @@
import axios from "axios";
import toast from "react-hot-toast";
-const URL = "http://localhost:5000";
+const URL = process.env.BACKEND_URL;
export const getInvestorsFromDb = async () => {
try {
From 31bd6a2fa1e6240f694e5bec620141971a064824 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 9 Sep 2025 19:16:11 +0500
Subject: [PATCH 17/49] auth url changings
---
src/context/AuthContext.tsx | 6 +-
src/context/SocketContext.tsx | 2 +-
src/data/collaborationRequests.ts | 48 ---------
src/data/messages.ts | 100 ------------------
src/pages/entrepreneurs/EntrepreneursPage.tsx | 1 -
5 files changed, 4 insertions(+), 153 deletions(-)
diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx
index 72991a644..08ad5dfc1 100644
--- a/src/context/AuthContext.tsx
+++ b/src/context/AuthContext.tsx
@@ -9,7 +9,7 @@ const AuthContext = createContext(undefined);
// Local storage keys
const USER_STORAGE_KEY = "business_nexus_user";
const RESET_TOKEN_KEY = "business_nexus_reset_token";
-const URL = "http://localhost:5000";
+const URL = process.env.BACKEND_URL;
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
@@ -22,7 +22,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
const token = localStorage.getItem("token");
if (token) {
axios
- .get("http://localhost:5000/auth/verify", {
+ .get(URL+"/auth/verify", {
headers: { Authorization: `Bearer ${token}` },
})
.then((res) => {
@@ -120,7 +120,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
localStorage.setItem(RESET_TOKEN_KEY, resetToken);
// In a real app, this would send an email
- const resetLink = `http://localhost:5173/reset-password?token=${resetToken}`;
+ const resetLink = `https://nexus-gso984fz0-danish-ajmals-projects.vercel.app/reset-password?token=${resetToken}`;
const message = `
To reset your password, please click the button below:
= ({
useEffect(() => {
if (user) {
// connect socket after login
- const s = io("http://localhost:5000", {
+ const s = io(process.env.BACKEND_URL, {
withCredentials: true,
transports: ["websocket", "polling"],
});
diff --git a/src/data/collaborationRequests.ts b/src/data/collaborationRequests.ts
index e1d6cff82..80bb007d2 100644
--- a/src/data/collaborationRequests.ts
+++ b/src/data/collaborationRequests.ts
@@ -2,54 +2,6 @@ import axios from "axios";
import { CollaborationRequest } from "../types";
import toast from "react-hot-toast";
-export const collaborationRequests: CollaborationRequest[] = [
- {
- id: "req1",
- investorId: "i1",
- entrepreneurId: "e1",
- message:
- "Id like to explore potential investment in TechWave AI. Your AI-driven financial analytics platform aligns well with my investment thesis.",
- status: "pending",
- createdAt: "2023-08-10T15:30:00Z",
- },
- {
- id: "req2",
- investorId: "i2",
- entrepreneurId: "e1",
- message:
- "Interested in discussing how TechWave AI can incorporate sustainable practices. Lets connect to explore potential collaboration.",
- status: "accepted",
- createdAt: "2023-08-05T11:45:00Z",
- },
- {
- id: "req3",
- investorId: "i3",
- entrepreneurId: "e3",
- message:
- "Your HealthPulse platform addresses a critical need in mental healthcare. Id like to learn more about your traction and roadmap.",
- status: "pending",
- createdAt: "2023-08-12T09:20:00Z",
- },
- {
- id: "req4",
- investorId: "i2",
- entrepreneurId: "e2",
- message:
- "GreenLifes biodegradable packaging solutions align with my focus on sustainable investments. Lets discuss scaling possibilities.",
- status: "accepted",
- createdAt: "2023-07-28T14:15:00Z",
- },
- {
- id: "req5",
- investorId: "i1",
- entrepreneurId: "e4",
- message:
- "Your UrbanFarm concept is fascinating. Im interested in learning more about your IoT implementation and market validation.",
- status: "rejected",
- createdAt: "2023-08-03T16:50:00Z",
- },
-];
-
const URL = process.env.BACKEND_URL;
// Helper function to get collaboration requests for an entrepreneur
export const getRequestsForEntrepreneur = async (
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 8922dcf4b..f9be9ef59 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -1,104 +1,4 @@
import axios from "axios";
-import { Message, ChatConversation } from "../types";
-
-export const messages: Message[] = [
- // Conversation between Sarah (e1) and Michael (i1)
- {
- id: "m1",
- senderId: "e1",
- receiverId: "i1",
- content:
- "Thanks for connecting. Id love to discuss how our AI platform can revolutionize financial analytics for SMBs.",
- timestamp: "2023-08-15T10:15:00Z",
- isRead: true,
- },
- {
- id: "m2",
- senderId: "i1",
- receiverId: "e1",
- content:
- "Im interested in learning more about your tech stack and ML models. Are you available for a call this week?",
- timestamp: "2023-08-15T10:30:00Z",
- isRead: true,
- },
- {
- id: "m3",
- senderId: "e1",
- receiverId: "i1",
- content:
- "Absolutely! I can walk you through our technology and current traction. How does Thursday at 2pm PT work?",
- timestamp: "2023-08-15T10:45:00Z",
- isRead: true,
- },
- {
- id: "m4",
- senderId: "i1",
- receiverId: "e1",
- content:
- "Thursday works great. Ill send a calendar invite. Looking forward to it!",
- timestamp: "2023-08-15T11:00:00Z",
- isRead: false,
- },
-
- // Conversation between Maya (e3) and Jennifer (i2)
- {
- id: "m5",
- senderId: "i2",
- receiverId: "e3",
- content:
- "I saw your pitch for HealthPulse and Im intrigued by your approach to mental healthcare accessibility.",
- timestamp: "2023-08-16T09:00:00Z",
- isRead: true,
- },
- {
- id: "m6",
- senderId: "e3",
- receiverId: "i2",
- content:
- "Thank you, Jennifer! Mental health services need to be more accessible, especially in underserved communities.",
- timestamp: "2023-08-16T09:15:00Z",
- isRead: true,
- },
- {
- id: "m7",
- senderId: "i2",
- receiverId: "e3",
- content:
- "I completely agree. Could you share more about your user acquisition strategy and current metrics?",
- timestamp: "2023-08-16T09:30:00Z",
- isRead: false,
- },
-
- // Conversation between David (e2) and Robert (i3)
- {
- id: "m8",
- senderId: "e2",
- receiverId: "i3",
- content:
- "Hello Robert, I noticed you invest in healthcare. While GreenLife is focused on sustainable packaging, we have some applications in medical supplies.",
- timestamp: "2023-08-17T14:00:00Z",
- isRead: true,
- },
- {
- id: "m9",
- senderId: "i3",
- receiverId: "e2",
- content:
- "Interesting crossover, David. Id be interested in learning more about your biodegradable materials and how they could be used in healthcare.",
- timestamp: "2023-08-17T15:30:00Z",
- isRead: true,
- },
- {
- id: "m10",
- senderId: "e2",
- receiverId: "i3",
- content:
- "Great! Weve been developing materials that can safely package medical devices while being eco-friendly. Our tests show 40% less environmental impact.",
- timestamp: "2023-08-17T16:45:00Z",
- isRead: false,
- },
-];
-
const URL = process.env.BACKEND_URL;
// Helper function to get messages between two users
export const getMessagesBetweenUsers = async (
diff --git a/src/pages/entrepreneurs/EntrepreneursPage.tsx b/src/pages/entrepreneurs/EntrepreneursPage.tsx
index 60037acc0..530478b6c 100644
--- a/src/pages/entrepreneurs/EntrepreneursPage.tsx
+++ b/src/pages/entrepreneurs/EntrepreneursPage.tsx
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
import { Search, Filter, MapPin } from 'lucide-react';
import { Input } from '../../components/ui/Input';
import { Card, CardHeader, CardBody } from '../../components/ui/Card';
-import { Badge } from '../../components/ui/Badge';
import { EntrepreneurCard } from '../../components/entrepreneur/EntrepreneurCard';
import { useAuth } from '../../context/AuthContext';
import { getEnterprenuerFromDb } from '../../data/users';
From 2585d9493549d6646ecd732222b91314f5d6959d Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 9 Sep 2025 19:45:21 +0500
Subject: [PATCH 18/49] vite envoironment added
---
.env | 1 -
src/context/AuthContext.tsx | 3 ++-
src/context/SocketContext.tsx | 2 +-
src/data/collaborationRequests.ts | 2 +-
src/data/messages.ts | 2 +-
src/data/users.ts | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
delete mode 100644 .env
diff --git a/.env b/.env
deleted file mode 100644
index 9ce68e432..000000000
--- a/.env
+++ /dev/null
@@ -1 +0,0 @@
-BACKEND_URL="http://nexus-backend-production-02aa.up.railway.app"
\ No newline at end of file
diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx
index 08ad5dfc1..4a2194f0d 100644
--- a/src/context/AuthContext.tsx
+++ b/src/context/AuthContext.tsx
@@ -9,7 +9,8 @@ const AuthContext = createContext(undefined);
// Local storage keys
const USER_STORAGE_KEY = "business_nexus_user";
const RESET_TOKEN_KEY = "business_nexus_reset_token";
-const URL = process.env.BACKEND_URL;
+const URL = import.meta.env.VITE_BACKEND_URL;
+console.log(URL)
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
diff --git a/src/context/SocketContext.tsx b/src/context/SocketContext.tsx
index 9a9f210e8..678fd0966 100644
--- a/src/context/SocketContext.tsx
+++ b/src/context/SocketContext.tsx
@@ -19,7 +19,7 @@ export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
useEffect(() => {
if (user) {
// connect socket after login
- const s = io(process.env.BACKEND_URL, {
+ const s = io(import.meta.env.VITE_BACKEND_URL, {
withCredentials: true,
transports: ["websocket", "polling"],
});
diff --git a/src/data/collaborationRequests.ts b/src/data/collaborationRequests.ts
index 80bb007d2..4733425e9 100644
--- a/src/data/collaborationRequests.ts
+++ b/src/data/collaborationRequests.ts
@@ -2,7 +2,7 @@ import axios from "axios";
import { CollaborationRequest } from "../types";
import toast from "react-hot-toast";
-const URL = process.env.BACKEND_URL;
+const URL = import.meta.env.VITE_BACKEND_URL;
// Helper function to get collaboration requests for an entrepreneur
export const getRequestsForEntrepreneur = async (
enter_id: string
diff --git a/src/data/messages.ts b/src/data/messages.ts
index f9be9ef59..5bdd28a03 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -1,5 +1,5 @@
import axios from "axios";
-const URL = process.env.BACKEND_URL;
+const URL = import.meta.env.VITE_BACKEND_URL;
// Helper function to get messages between two users
export const getMessagesBetweenUsers = async (
user1Id: string,
diff --git a/src/data/users.ts b/src/data/users.ts
index c93de9029..973311b2b 100644
--- a/src/data/users.ts
+++ b/src/data/users.ts
@@ -1,6 +1,6 @@
import axios from "axios";
import toast from "react-hot-toast";
-const URL = process.env.BACKEND_URL;
+const URL = import.meta.env.VITE_BACKEND_URL;
export const getInvestorsFromDb = async () => {
try {
From 191b67431cc58c668f14f48e7746c41c303ee8b1 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Wed, 10 Sep 2025 13:29:41 +0500
Subject: [PATCH 19/49] deals document and notificaiton page added
---
src/components/chat/ChatUserList.tsx | 5 +-
src/context/AuthContext.tsx | 3 -
src/data/messages.ts | 4 +-
src/data/users.ts | 1 -
src/pages/chat/ChatPage.tsx | 29 +-
src/pages/dashboard/EntrepreneurDashboard.tsx | 14 +-
src/pages/dashboard/InvestorDashboard.tsx | 44 +-
src/pages/deals/DealsPage.tsx | 458 ++++++++++--------
src/pages/documents/DocumentsPage.tsx | 135 +++---
src/pages/investors/InvestorsPage.tsx | 11 +-
src/pages/messages/MessagesPage.tsx | 42 +-
src/pages/notifications/NotificationsPage.tsx | 156 +++---
src/pages/profile/InvestorProfile.tsx | 1 -
13 files changed, 438 insertions(+), 465 deletions(-)
diff --git a/src/components/chat/ChatUserList.tsx b/src/components/chat/ChatUserList.tsx
index c98d9ea05..851208747 100644
--- a/src/components/chat/ChatUserList.tsx
+++ b/src/components/chat/ChatUserList.tsx
@@ -25,7 +25,6 @@ export const ChatUserList: React.FC = ({ conversation }) => {
const allIds = Array.from(
new Set(conversation.participants.flatMap((i) => i.receiverId || ""))
);
-
const users = await Promise.all(
allIds.map(async (id) => await getUserFromDb(id))
);
@@ -48,7 +47,7 @@ export const ChatUserList: React.FC = ({ conversation }) => {
};
return (
-
+
Messages
@@ -100,7 +99,7 @@ export const ChatUserList: React.FC = ({ conversation }) => {
{Object.keys(lastMessage).length !== 0 && (
{formatDistanceToNow(
- new Date(lastMessage.time || "Text first"),
+ new Date(lastMessage?.time || "Text first"),
{ addSuffix: false }
)}
diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx
index 4a2194f0d..a71fa9552 100644
--- a/src/context/AuthContext.tsx
+++ b/src/context/AuthContext.tsx
@@ -10,7 +10,6 @@ const AuthContext = createContext(undefined);
const USER_STORAGE_KEY = "business_nexus_user";
const RESET_TOKEN_KEY = "business_nexus_reset_token";
const URL = import.meta.env.VITE_BACKEND_URL;
-console.log(URL)
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
@@ -197,7 +196,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
userData.bio === "" &&
userData.avatarUrl === ""
) {
- console.log(userData);
alert("Make changes to update profile..");
return;
}
@@ -207,7 +205,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
formData.append("email", userData.email);
formData.append("bio", userData.bio);
formData.append("avatarUrl", userData.avatarUrl);
- console.log(formData);
await axios
.post(`${URL}/user/update-profile/${userId}`, formData, {
withCredentials: true,
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 5bdd28a03..84c615125 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -20,7 +20,6 @@ export const saveMessagesBetweenUsers = async (newMessage: Any) => {
const res = await axios.post(`${URL}/message/save-message`, newMessage, {
withCredentials: true,
});
- console.log("message saved");
const { message } = res.data;
return message;
} catch (err) {
@@ -30,11 +29,10 @@ export const saveMessagesBetweenUsers = async (newMessage: Any) => {
// Helper function to get conversations for a user
export const getConversationsForUser = async (
currentUserId: string | undefined,
- partnerId: string | undefined
) => {
// Get unique conversation partners
const res = await axios.get(
- `${URL}/conversation/get-conversations-for-user?currentUserId=${currentUserId}&partnerId=${partnerId}`,
+ `${URL}/conversation/get-conversations-for-user?currentUserId=${currentUserId}`,
{
withCredentials: true,
}
diff --git a/src/data/users.ts b/src/data/users.ts
index 973311b2b..d668db8a5 100644
--- a/src/data/users.ts
+++ b/src/data/users.ts
@@ -47,7 +47,6 @@ export const getEnterpreneurById = async (id) => {
}
);
const { entrepreneur } = res.data;
- console.log(entrepreneur);
return entrepreneur;
} catch (err) {
console.log(err);
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 529802d12..582c99a9f 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -17,7 +17,6 @@ import {
import { MessageCircle } from "lucide-react";
import { getUserFromDb } from "../../data/users";
import { useSocket } from "../../context/SocketContext";
-import IncomingCallModal from "../../components/webrtc/IncomingCallModal";
export const ChatPage: React.FC = () => {
const { userId } = useParams<{ userId: string }>();
@@ -32,33 +31,12 @@ export const ChatPage: React.FC = () => {
const [users, setUsers] = useState<[string, User][]>([]);
const { socket } = useSocket();
- // const [incomingCall, setIncomingCall] = useState<{
- // from: string;
- // roomId: string;
- // } | null>(null);
const navigate = useNavigate();
- // const acceptCall = () => {
- // if (incomingCall) {
- // socket?.emit("accept-call", {
- // to: incomingCall.from,
- // });
- // navigate(`audio-call/${incomingCall.roomId}`);
- // setIncomingCall(null);
- // }
- // };
-
- // const rejectCall = () => {
- // if (incomingCall) {
- // socket?.emit("reject-call", { to: incomingCall.from });
- // setIncomingCall(null);
- // }
- // };
-
// Load conversations
useEffect(() => {
const fetchConversation = async () => {
- const conv = await getConversationsForUser(currentUser?.userId, userId);
+ const conv = await getConversationsForUser(currentUser?.userId);
if (conv) setConversation(conv);
};
fetchConversation();
@@ -108,10 +86,6 @@ export const ChatPage: React.FC = () => {
// Connect socket.io client
useEffect(() => {
- // socket?.on("incoming-call", ({ from, roomId }) => {
- // setIncomingCall({ from, roomId });
- // });
-
// when user receive message
socket?.on("received-message", (message) => {
setMessages((prev) => [...prev, message]);
@@ -132,7 +106,6 @@ export const ChatPage: React.FC = () => {
return () => {
socket?.off("send-messsage");
socket?.off("received-messsage");
- // socket?.off("incoming-call");
socket?.off("accept-call");
socket?.off("reject-call");
};
diff --git a/src/pages/dashboard/EntrepreneurDashboard.tsx b/src/pages/dashboard/EntrepreneurDashboard.tsx
index 1672314b5..ecfc6ef39 100644
--- a/src/pages/dashboard/EntrepreneurDashboard.tsx
+++ b/src/pages/dashboard/EntrepreneurDashboard.tsx
@@ -24,23 +24,11 @@ export const EntrepreneurDashboard: React.FC = () => {
CollaborationRequest[]
>([]);
const [recommendedInvestors, setRecommendedInvestors] = useState([]);
-
- useEffect(() => {
- const fetchData = async () => {
- if (user) {
- const investors = await getInvestorsFromDb();
- console.log(investors);
- setRecommendedInvestors(investors);
- }
- };
- fetchData();
- }, []);
-
+
useEffect(() => {
const fetchData = async () => {
if (user) {
const investors = await getInvestorsFromDb();
- console.log(investors);
setRecommendedInvestors(investors);
}
};
diff --git a/src/pages/dashboard/InvestorDashboard.tsx b/src/pages/dashboard/InvestorDashboard.tsx
index 888c51ea1..bc840e7fd 100644
--- a/src/pages/dashboard/InvestorDashboard.tsx
+++ b/src/pages/dashboard/InvestorDashboard.tsx
@@ -9,23 +9,20 @@ import { EntrepreneurCard } from "../../components/entrepreneur/EntrepreneurCard
import { useAuth } from "../../context/AuthContext";
import { getRequestsFromInvestor } from "../../data/collaborationRequests";
import { getEnterprenuerFromDb } from "../../data/users";
-import { CollaborationRequest } from "../../types";
+import { CollaborationRequest, Entrepreneur } from "../../types";
export const InvestorDashboard: React.FC = () => {
const { user } = useAuth();
const [searchQuery, setSearchQuery] = useState("");
const [selectedIndustries, setSelectedIndustries] = useState([]);
- console.log(user);
if (!user) return null;
// Get collaboration requests sent by this investor
// const requestedEntrepreneurIds = sentRequests.map(req => req.entrepreneurId);
- const [entrepreneurs, setEnterprenuers] = useState([]);
+ const [entrepreneurs, setEnterprenuers] = useState([]);
const [sentRequests, setSentRequests] = useState([]);
- const industries = [];
-
useEffect(() => {
const fetchData = async () => {
if (user) {
@@ -48,21 +45,28 @@ export const InvestorDashboard: React.FC = () => {
}, [user.userId]);
// Filter entrepreneurs based on search and industry filters
- const filteredEntrepreneurs = entrepreneurs;
- // const filteredEntrepreneurs = entrepreneurs.filter(entrepreneur => {
- // // Search filter
- // const matchesSearch = searchQuery === '' ||
- // entrepreneur.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
- // entrepreneur.startupName.toLowerCase().includes(searchQuery.toLowerCase()) ||
- // entrepreneur.industry.toLowerCase().includes(searchQuery.toLowerCase()) ||
- // entrepreneur.pitchSummary.toLowerCase().includes(searchQuery.toLowerCase());
-
- // // Industry filter
- // const matchesIndustry = selectedIndustries.length === 0 ||
- // selectedIndustries.includes(entrepreneur.industry);
-
- // return matchesSearch && matchesIndustry;
- // });
+ const filteredEntrepreneurs = entrepreneurs.filter((entrepreneur) => {
+ // Search filter
+ const matchesSearch =
+ searchQuery === "" ||
+ entrepreneur.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ entrepreneur.startupName
+ .toLowerCase()
+ .includes(searchQuery.toLowerCase()) ||
+ entrepreneur.industry.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ entrepreneur.pitchSummary
+ .toLowerCase()
+ .includes(searchQuery.toLowerCase());
+
+ // Industry filter
+ const matchesIndustry =
+ selectedIndustries.length === 0 ||
+ selectedIndustries.includes(entrepreneur.industry);
+
+ return matchesSearch && matchesIndustry;
+ });
+
+ const industries = entrepreneurs && entrepreneurs.map((enter) => enter.industry);
// Get unique industries for filter
diff --git a/src/pages/deals/DealsPage.tsx b/src/pages/deals/DealsPage.tsx
index ea26870c5..85c1f5dcc 100644
--- a/src/pages/deals/DealsPage.tsx
+++ b/src/pages/deals/DealsPage.tsx
@@ -1,156 +1,186 @@
-import React, { useState } from 'react';
-import { Search, Filter, DollarSign, TrendingUp, Users, Calendar } from 'lucide-react';
-import { Card, CardHeader, CardBody } from '../../components/ui/Card';
-import { Input } from '../../components/ui/Input';
-import { Button } from '../../components/ui/Button';
-import { Badge } from '../../components/ui/Badge';
-import { Avatar } from '../../components/ui/Avatar';
+import React, { useState } from "react";
+import {
+ Search,
+ Filter,
+ DollarSign,
+ TrendingUp,
+ Users,
+ Calendar,
+} from "lucide-react";
+import { Card, CardHeader, CardBody } from "../../components/ui/Card";
+import { Input } from "../../components/ui/Input";
+import { Button } from "../../components/ui/Button";
+import { Badge } from "../../components/ui/Badge";
+import { Avatar } from "../../components/ui/Avatar";
-const deals = [
- {
- id: 1,
- startup: {
- name: 'TechWave AI',
- logo: 'https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg',
- industry: 'FinTech'
- },
- amount: '$1.5M',
- equity: '15%',
- status: 'Due Diligence',
- stage: 'Series A',
- lastActivity: '2024-02-15'
- },
- {
- id: 2,
- startup: {
- name: 'GreenLife Solutions',
- logo: 'https://images.pexels.com/photos/614810/pexels-photo-614810.jpeg',
- industry: 'CleanTech'
- },
- amount: '$2M',
- equity: '20%',
- status: 'Term Sheet',
- stage: 'Seed',
- lastActivity: '2024-02-10'
- },
- {
- id: 3,
- startup: {
- name: 'HealthPulse',
- logo: 'https://images.pexels.com/photos/415829/pexels-photo-415829.jpeg',
- industry: 'HealthTech'
- },
- amount: '$800K',
- equity: '12%',
- status: 'Negotiation',
- stage: 'Pre-seed',
- lastActivity: '2024-02-05'
- }
-];
+interface Deal {
+ id: number;
+ startup: {
+ name: string;
+ logo: string;
+ industry: string;
+ };
+ amount: string;
+ equity: string;
+ status: string;
+ stage: string;
+ lastActivity: string;
+}
export const DealsPage: React.FC = () => {
- const [searchQuery, setSearchQuery] = useState('');
+ const [deals, setDeals] = useState([]);
+ const [searchQuery, setSearchQuery] = useState("");
const [selectedStatus, setSelectedStatus] = useState([]);
-
- const statuses = ['Due Diligence', 'Term Sheet', 'Negotiation', 'Closed', 'Passed'];
-
+ const [showForm, setShowForm] = useState(false);
+ const [newDeal, setNewDeal] = useState({
+ name: "",
+ logo: "",
+ industry: "",
+ amount: "",
+ equity: "",
+ status: "Due Diligence",
+ stage: "Seed",
+ });
+
+ const statuses = ["Due Diligence", "Term Sheet", "Negotiation", "Closed", "Passed"];
+
const toggleStatus = (status: string) => {
- setSelectedStatus(prev =>
- prev.includes(status)
- ? prev.filter(s => s !== status)
- : [...prev, status]
+ setSelectedStatus((prev) =>
+ prev.includes(status) ? prev.filter((s) => s !== status) : [...prev, status]
);
};
-
+
const getStatusColor = (status: string) => {
switch (status) {
- case 'Due Diligence':
- return 'primary';
- case 'Term Sheet':
- return 'secondary';
- case 'Negotiation':
- return 'accent';
- case 'Closed':
- return 'success';
- case 'Passed':
- return 'error';
+ case "Due Diligence":
+ return "primary";
+ case "Term Sheet":
+ return "secondary";
+ case "Negotiation":
+ return "accent";
+ case "Closed":
+ return "success";
+ case "Passed":
+ return "error";
default:
- return 'gray';
+ return "gray";
}
};
-
+
+ const handleAddDeal = () => {
+ if (!newDeal.name || !newDeal.amount) return;
+
+ const deal: Deal = {
+ id: Date.now(),
+ startup: {
+ name: newDeal.name,
+ logo: newDeal.logo || "https://via.placeholder.com/50",
+ industry: newDeal.industry || "Unknown",
+ },
+ amount: newDeal.amount,
+ equity: newDeal.equity,
+ status: newDeal.status,
+ stage: newDeal.stage,
+ lastActivity: new Date().toISOString(),
+ };
+
+ setDeals((prev) => [...prev, deal]);
+ setNewDeal({
+ name: "",
+ logo: "",
+ industry: "",
+ amount: "",
+ equity: "",
+ status: "Due Diligence",
+ stage: "Seed",
+ });
+ setShowForm(false);
+ };
+
+ const filteredDeals = deals.filter(
+ (deal) =>
+ (deal.startup.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ deal.startup.industry.toLowerCase().includes(searchQuery.toLowerCase())) &&
+ (selectedStatus.length === 0 || selectedStatus.includes(deal.status))
+ );
+
return (
Investment Deals
-
Track and manage your investment pipeline
+
+ Track and manage your investment pipeline
+
-
-
- Add Deal
+
+ setShowForm(!showForm)}>
+ {showForm ? "Cancel" : "Add Deal"}
-
- {/* Stats */}
-
-
-
-
-
-
-
-
-
Total Investment
-
$4.3M
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Portfolio Companies
-
12
-
-
-
-
-
+
+ {/* Add Deal Form */}
+ {showForm && (
-
-
-
-
-
-
-
Closed This Month
-
2
-
-
+
+ New Deal
+
+
+ setNewDeal({ ...newDeal, name: e.target.value })}
+ />
+ setNewDeal({ ...newDeal, logo: e.target.value })}
+ />
+
+ setNewDeal({ ...newDeal, industry: e.target.value })
+ }
+ />
+
+ setNewDeal({ ...newDeal, amount: e.target.value })
+ }
+ />
+
+ setNewDeal({ ...newDeal, equity: e.target.value })
+ }
+ />
+ setNewDeal({ ...newDeal, stage: e.target.value })}
+ />
+
+ setNewDeal({ ...newDeal, status: e.target.value })
+ }
+ >
+ {statuses.map((status) => (
+
+ {status}
+
+ ))}
+
+
+ Save Deal
-
-
+ )}
+
{/* Filters */}
@@ -162,15 +192,19 @@ export const DealsPage: React.FC = () => {
fullWidth
/>
-
+
- {statuses.map(status => (
+ {statuses.map((status) => (
toggleStatus(status)}
>
@@ -181,92 +215,96 @@ export const DealsPage: React.FC = () => {
-
+
{/* Deals table */}
Active Deals
-
-
-
-
-
- Startup
-
-
- Amount
-
-
- Equity
-
-
- Status
-
-
- Stage
-
-
- Last Activity
-
-
- Actions
-
-
-
-
- {deals.map(deal => (
-
-
-
-
-
-
- {deal.startup.name}
-
-
- {deal.startup.industry}
+ {filteredDeals.length === 0 ? (
+
No deals found.
+ ) : (
+
+
+
+
+
+ Startup
+
+
+ Amount
+
+
+ Equity
+
+
+ Status
+
+
+ Stage
+
+
+ Last Activity
+
+
+ Actions
+
+
+
+
+ {filteredDeals.map((deal) => (
+
+
+
+
+
+
+ {deal.startup.name}
+
+
+ {deal.startup.industry}
+
-
-
-
- {deal.amount}
-
-
- {deal.equity}
-
-
-
- {deal.status}
-
-
-
- {deal.stage}
-
-
-
- {new Date(deal.lastActivity).toLocaleDateString()}
-
-
-
-
- View Details
-
-
-
- ))}
-
-
-
+
+
+ {deal.amount}
+
+
+ {deal.equity}
+
+
+
+ {deal.status}
+
+
+
+ {deal.stage}
+
+
+
+ {new Date(deal.lastActivity).toLocaleDateString()}
+
+
+
+
+ View Details
+
+
+
+ ))}
+
+
+
+ )}
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/documents/DocumentsPage.tsx b/src/pages/documents/DocumentsPage.tsx
index 1e325d3c9..f3e5ffa2d 100644
--- a/src/pages/documents/DocumentsPage.tsx
+++ b/src/pages/documents/DocumentsPage.tsx
@@ -1,45 +1,37 @@
-import React from 'react';
-import { FileText, Upload, Download, Trash2, Share2 } from 'lucide-react';
-import { Card, CardHeader, CardBody } from '../../components/ui/Card';
-import { Button } from '../../components/ui/Button';
-import { Badge } from '../../components/ui/Badge';
+import React, { useState } from "react";
+import { FileText, Upload, Download, Trash2, Share2 } from "lucide-react";
+import { Card, CardHeader, CardBody } from "../../components/ui/Card";
+import { Button } from "../../components/ui/Button";
+import { Badge } from "../../components/ui/Badge";
-const documents = [
- {
- id: 1,
- name: 'Pitch Deck 2024.pdf',
- type: 'PDF',
- size: '2.4 MB',
- lastModified: '2024-02-15',
- shared: true
- },
- {
- id: 2,
- name: 'Financial Projections.xlsx',
- type: 'Spreadsheet',
- size: '1.8 MB',
- lastModified: '2024-02-10',
- shared: false
- },
- {
- id: 3,
- name: 'Business Plan.docx',
- type: 'Document',
- size: '3.2 MB',
- lastModified: '2024-02-05',
- shared: true
- },
- {
- id: 4,
- name: 'Market Research.pdf',
- type: 'PDF',
- size: '5.1 MB',
- lastModified: '2024-01-28',
- shared: false
- }
-];
+interface Document {
+ id: number;
+ name: string;
+ type: string;
+ size: string;
+ lastModified: string;
+ shared: boolean;
+}
export const DocumentsPage: React.FC = () => {
+ const [documents, setDocuments] = useState
([]);
+
+ const handleUpload = (e: React.ChangeEvent) => {
+ const files = e.target.files;
+ if (!files) return;
+
+ const uploadedDocs: Document[] = Array.from(files).map((file, idx) => ({
+ id: documents.length + idx + 1,
+ name: file.name,
+ type: file.type || "Unknown",
+ size: (file.size / (1024 * 1024)).toFixed(2) + " MB",
+ lastModified: new Date(file.lastModified).toISOString().split("T")[0],
+ shared: false,
+ }));
+
+ setDocuments((prev) => [...prev, ...uploadedDocs]);
+ };
+
return (
@@ -47,12 +39,20 @@ export const DocumentsPage: React.FC = () => {
Documents
Manage your startup's important files
-
-
}>
- Upload Document
-
+
+
+
+ } asChild>
+ Upload Document
+
+
-
+
{/* Storage info */}
@@ -66,39 +66,26 @@ export const DocumentsPage: React.FC = () => {
12.5 GB
Available
7.5 GB
-
-
-
Quick Access
-
-
- Recent Files
-
-
- Shared with Me
-
-
- Starred
-
-
- Trash
-
-
-
-
+
{/* Document list */}
- All Documents
+
+ All Documents
+
Sort by
@@ -110,7 +97,7 @@ export const DocumentsPage: React.FC = () => {
- {documents.map(doc => (
+ {documents.map((doc) => (
{
-
+
{doc.name}
{doc.shared && (
- Shared
+
+ Shared
+
)}
-
+
{doc.type}
{doc.size}
Modified {doc.lastModified}
-
+
{
>
-
+
{
>
-
+
{
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/investors/InvestorsPage.tsx b/src/pages/investors/InvestorsPage.tsx
index 230a7e2cc..30ef59387 100644
--- a/src/pages/investors/InvestorsPage.tsx
+++ b/src/pages/investors/InvestorsPage.tsx
@@ -21,10 +21,7 @@ export const InvestorsPage: React.FC = () => {
const fetchData = async () => {
if (user) {
const investors = await getInvestorsFromDb();
- console.log(investors);
setInvestors(investors ? investors : []);
-
- const requests = getRequestsForEntrepreneur(user.id);
}
};
fetchData();
@@ -39,13 +36,13 @@ export const InvestorsPage: React.FC = () => {
);
// Filter investors based on search and filters
- const filteredInvestors = investors.filter((investor) => {
+ const filteredInvestors = investors && investors.filter((investor) => {
const matchesSearch =
searchQuery === "" ||
- investor.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
- investor.bio.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ investor.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ investor.bio?.toLowerCase().includes(searchQuery.toLowerCase()) ||
investor.investmentInterests?.some((interest) =>
- interest.toLowerCase().includes(searchQuery.toLowerCase())
+ interest?.toLowerCase().includes(searchQuery.toLowerCase())
);
const matchesStages =
diff --git a/src/pages/messages/MessagesPage.tsx b/src/pages/messages/MessagesPage.tsx
index 1ee7b2295..b6d9c31e8 100644
--- a/src/pages/messages/MessagesPage.tsx
+++ b/src/pages/messages/MessagesPage.tsx
@@ -1,20 +1,33 @@
-import React from 'react';
-import { useAuth } from '../../context/AuthContext';
-import { getConversationsForUser } from '../../data/messages';
-import { ChatUserList } from '../../components/chat/ChatUserList';
-import { MessageCircle } from 'lucide-react';
+import React, { useEffect, useState } from "react";
+import { useAuth } from "../../context/AuthContext";
+import { getConversationsForUser } from "../../data/messages";
+import { ChatUserList } from "../../components/chat/ChatUserList";
+import { MessageCircle } from "lucide-react";
+import { ChatConversation } from "../../types";
export const MessagesPage: React.FC = () => {
const { user } = useAuth();
-
+ const [conversation, setConversation] = useState
();
+
if (!user) return null;
-
- const conversations = getConversationsForUser(user.userId);
-
+
+ useEffect(() => {
+ const fetchConversation = async () => {
+ const conv = await getConversationsForUser(user?.userId);
+ if (conv) {
+ setConversation(conv);
+ }
+ };
+
+ fetchConversation();
+ }, [user?.userId]);
+
return (
-
- {conversations.length > 0 ? (
-
+
+ {conversation? (
+
+
+
) : (
@@ -22,10 +35,11 @@ export const MessagesPage: React.FC = () => {
No messages yet
- Start connecting with entrepreneurs and investors to begin conversations
+ Start connecting with entrepreneurs and investors to begin
+ conversation
)}
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/notifications/NotificationsPage.tsx b/src/pages/notifications/NotificationsPage.tsx
index 3bb806c25..5872c07eb 100644
--- a/src/pages/notifications/NotificationsPage.tsx
+++ b/src/pages/notifications/NotificationsPage.tsx
@@ -1,112 +1,90 @@
-import React from 'react';
-import { Bell, MessageCircle, UserPlus, DollarSign } from 'lucide-react';
-import { Card, CardBody } from '../../components/ui/Card';
-import { Avatar } from '../../components/ui/Avatar';
-import { Badge } from '../../components/ui/Badge';
-import { Button } from '../../components/ui/Button';
-
-const notifications = [
- {
- id: 1,
- type: 'message',
- user: {
- name: 'Sarah Johnson',
- avatar: 'https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg'
- },
- content: 'sent you a message about your startup',
- time: '5 minutes ago',
- unread: true
- },
- {
- id: 2,
- type: 'connection',
- user: {
- name: 'Michael Rodriguez',
- avatar: 'https://images.pexels.com/photos/2379004/pexels-photo-2379004.jpeg'
- },
- content: 'accepted your connection request',
- time: '2 hours ago',
- unread: true
- },
- {
- id: 3,
- type: 'investment',
- user: {
- name: 'Jennifer Lee',
- avatar: 'https://images.pexels.com/photos/1181686/pexels-photo-1181686.jpeg'
- },
- content: 'showed interest in investing in your startup',
- time: '1 day ago',
- unread: false
- }
-];
+import React from "react";
+import { Bell, MessageCircle, UserPlus, DollarSign } from "lucide-react";
+import { Card, CardBody } from "../../components/ui/Card";
+import { Avatar } from "../../components/ui/Avatar";
+import { Badge } from "../../components/ui/Badge";
+import { Button } from "../../components/ui/Button";
+import { useAuth } from "../../context/AuthContext";
export const NotificationsPage: React.FC = () => {
+ const { user } = useAuth();
+ const notifications = user?.notifications || [];
+
const getNotificationIcon = (type: string) => {
switch (type) {
- case 'message':
+ case "message":
return
;
- case 'connection':
+ case "connection":
return
;
- case 'investment':
+ case "investment":
return
;
default:
return
;
}
};
-
+
return (
Notifications
-
Stay updated with your network activity
+
+ Stay updated with your network activity
+
-
-
- Mark all as read
-
+
+ {notifications.length > 0 && (
+
+ Mark all as read
+
+ )}
-
+
- {notifications.map(notification => (
-
-
-
-
-
-
-
- {notification.user.name}
-
- {notification.unread && (
- New
- )}
-
-
-
- {notification.content}
-
-
-
- {getNotificationIcon(notification.type)}
-
{notification.time}
+ {notifications.length > 0 ? (
+ notifications.map((notification: any) => (
+
+
+
+
+
+
+
+ {notification.user.name}
+
+ {notification.unread && (
+
+ New
+
+ )}
+
+
+
{notification.content}
+
+
+ {getNotificationIcon(notification.type)}
+ {notification.time}
+
-
-
-
- ))}
+
+
+ ))
+ ) : (
+
+ No notifications found 🚀
+
+ )}
);
-};
\ No newline at end of file
+};
diff --git a/src/pages/profile/InvestorProfile.tsx b/src/pages/profile/InvestorProfile.tsx
index 874786345..8f3480914 100644
--- a/src/pages/profile/InvestorProfile.tsx
+++ b/src/pages/profile/InvestorProfile.tsx
@@ -150,7 +150,6 @@ export const InvestorProfile: React.FC = () => {
investmentCriteria: updateCriteria,
criteria: "",
});
- console.log(updateCriteria);
};
return (
From b36bd651b2d7bac5903636f9e202527986a63a53 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Wed, 10 Sep 2025 13:43:53 +0500
Subject: [PATCH 20/49] time error resolved
---
src/components/chat/ChatMessage.tsx | 47 +++++++++++++++++-----------
src/components/chat/ChatUserList.tsx | 16 +++++-----
2 files changed, 37 insertions(+), 26 deletions(-)
diff --git a/src/components/chat/ChatMessage.tsx b/src/components/chat/ChatMessage.tsx
index c238686d5..f94922007 100644
--- a/src/components/chat/ChatMessage.tsx
+++ b/src/components/chat/ChatMessage.tsx
@@ -1,20 +1,26 @@
-import React from 'react';
-import { formatDistanceToNow } from 'date-fns';
-import { Message, User } from '../../types';
-import { Avatar } from '../ui/Avatar';
+import React from "react";
+import { formatDistanceToNow } from "date-fns";
+import { Message, User } from "../../types";
+import { Avatar } from "../ui/Avatar";
interface ChatMessageProps {
message: Message;
- user:User | undefined
+ user: User | undefined;
isCurrentUser: boolean;
}
-export const ChatMessage: React.FC = ({ message,user, isCurrentUser }) => {
+export const ChatMessage: React.FC = ({
+ message,
+ user,
+ isCurrentUser,
+}) => {
if (!user) return null;
-
+
return (
{!isCurrentUser && (
= ({ message,user, isCurren
className="mr-2 self-end"
/>
)}
-
-
+
+
-
-
- {formatDistanceToNow(new Date(message.time), { addSuffix: true })}
-
+ {message?.time && (
+
+ {formatDistanceToNow(new Date(message.time), { addSuffix: true })}
+
+ )}
-
+
{isCurrentUser && (
= ({ message,user, isCurren
)}
);
-};
\ No newline at end of file
+};
diff --git a/src/components/chat/ChatUserList.tsx b/src/components/chat/ChatUserList.tsx
index 851208747..2745b7e3e 100644
--- a/src/components/chat/ChatUserList.tsx
+++ b/src/components/chat/ChatUserList.tsx
@@ -96,14 +96,14 @@ export const ChatUserList: React.FC = ({ conversation }) => {
{"..."}
- {Object.keys(lastMessage).length !== 0 && (
-
- {formatDistanceToNow(
- new Date(lastMessage?.time || "Text first"),
- { addSuffix: false }
- )}
-
- )}
+ {Object.keys(lastMessage).length !== 0 &&
+ lastMessage?.time && (
+
+ {formatDistanceToNow(new Date(lastMessage.time), {
+ addSuffix: false,
+ })}
+
+ )}
From 38602aee25eb996d086f230ac76fe2e83e68d505 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 30 Sep 2025 13:29:08 +0500
Subject: [PATCH 21/49] agora RTC implemented for audio video call
---
package-lock.json | 172 ++++++++
package.json | 6 +
src/App.tsx | 10 +-
.../entrepreneur/EntrepreneurCard.tsx | 2 +-
src/components/investor/InvestorCard.tsx | 28 +-
src/components/layout/DashboardLayout.tsx | 27 +-
src/components/webrtc/IncomingCallModal.tsx | 10 +-
src/context/AuthContext.tsx | 24 +-
src/context/SocketContext.tsx | 3 +-
src/data/messages.ts | 21 +-
src/pages/chat/ChatPage.tsx | 5 +-
src/pages/dashboard/EntrepreneurDashboard.tsx | 1 +
src/pages/investors/InvestorsPage.tsx | 2 +-
src/pages/profile/InvestorProfile.tsx | 2 +-
src/pages/webRTC/AudioCall.tsx | 342 +++++++--------
src/pages/webRTC/Videocall.tsx | 397 ++++++++++--------
16 files changed, 654 insertions(+), 398 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index d5ae710ea..f12e42e7b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "business-nexus",
"version": "0.1.0",
"dependencies": {
+ "agora-rtc-sdk-ng": "^4.24.0",
"axios": "^1.6.7",
"date-fns": "^3.3.1",
"jwt-decode": "^4.0.0",
@@ -37,6 +38,39 @@
"vite": "^5.4.2"
}
},
+ "node_modules/@agora-js/media": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/@agora-js/media/-/media-4.24.0.tgz",
+ "integrity": "sha512-foii2klr5+qonLznxN0ZZFejoxLt/W8do79wmIsADPZLw2uZjRP35m0lqUGiLXBKeQ8u3i4UygPzEdFaY26hrw==",
+ "license": "MIT",
+ "dependencies": {
+ "@agora-js/report": "4.24.0",
+ "@agora-js/shared": "4.24.0",
+ "agora-rte-extension": "^1.2.4",
+ "axios": "^1.8.3",
+ "webrtc-adapter": "8.2.0"
+ }
+ },
+ "node_modules/@agora-js/report": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/@agora-js/report/-/report-4.24.0.tgz",
+ "integrity": "sha512-MYbtkdY1Ls0KW0iagUzrPzyvqMWlyCWSC5odEb1SQaraAl7DJeDUkf91a3wxKzrjVah+LCxFxsS4lCFDxvKgNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@agora-js/shared": "4.24.0",
+ "axios": "^1.8.3"
+ }
+ },
+ "node_modules/@agora-js/shared": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/@agora-js/shared/-/shared-4.24.0.tgz",
+ "integrity": "sha512-Vj67ZcTHZI+1ctWusrEPSSGLM3l6CFiAze/Bi8r7YHRMLivzhZR79nV6GiKvHS3muLAON2YAExznvjPIly6lcg==",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.8.3",
+ "ua-parser-js": "^0.7.34"
+ }
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -1584,6 +1618,29 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/agora-rtc-sdk-ng": {
+ "version": "4.24.0",
+ "resolved": "https://registry.npmjs.org/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.24.0.tgz",
+ "integrity": "sha512-2apG/07EtsuX21ncSF77q+dr6/kDgu9B/RpKtstCtaq46l4/Eraoecewi4zXRUCY3Im+8dzTIXx6jUwyPdxdHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@agora-js/media": "4.24.0",
+ "@agora-js/report": "4.24.0",
+ "@agora-js/shared": "4.24.0",
+ "agora-rte-extension": "^1.2.4",
+ "axios": "^1.8.3",
+ "formdata-polyfill": "^4.0.7",
+ "pako": "^2.1.0",
+ "ua-parser-js": "^0.7.34",
+ "webrtc-adapter": "8.2.0"
+ }
+ },
+ "node_modules/agora-rte-extension": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/agora-rte-extension/-/agora-rte-extension-1.2.4.tgz",
+ "integrity": "sha512-0ovZz1lbe30QraG1cU+ji7EnQ8aUu+Hf3F+a8xPml3wPOyUQEK6CTdxV9kMecr9t+fIDrGeW7wgJTsM1DQE7Nw==",
+ "license": "ISC"
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -2483,6 +2540,29 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -2605,6 +2685,18 @@
"node": ">= 6"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -3225,6 +3317,26 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true
},
+ "node_modules/node-domexception": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+ "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+ "deprecated": "Use your platform's native DOMException instead",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.5.0"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@@ -3319,6 +3431,12 @@
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true
},
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3850,6 +3968,12 @@
"loose-envify": "^1.1.0"
}
},
+ "node_modules/sdp": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz",
+ "integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==",
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@@ -4240,6 +4364,32 @@
}
}
},
+ "node_modules/ua-parser-js": {
+ "version": "0.7.41",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz",
+ "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "ua-parser-js": "script/cli.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
@@ -4351,6 +4501,28 @@
}
}
},
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/webrtc-adapter": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.0.tgz",
+ "integrity": "sha512-umxCMgedPAVq4Pe/jl3xmelLXLn4XZWFEMR5Iipb5wJ+k1xMX0yC4ZY9CueZUU1MjapFxai1tFGE7R/kotH6Ww==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "sdp": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0",
+ "npm": ">=3.10.0"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index b50efb5f1..b88b4b271 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "agora-rtc-sdk-ng": "^4.24.0",
"axios": "^1.6.7",
"date-fns": "^3.3.1",
"jwt-decode": "^4.0.0",
@@ -37,5 +38,10 @@
"typescript": "^5.5.3",
"typescript-eslint": "^8.3.0",
"vite": "^5.4.2"
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "npm run lint"
+ }
}
}
diff --git a/src/App.tsx b/src/App.tsx
index 60cc78901..5938ad00a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -104,8 +104,14 @@ function App() {
{/* Chat Routes */}
}>
- } />
- } />
+ }
+ />
+ }
+ />
} />
diff --git a/src/components/entrepreneur/EntrepreneurCard.tsx b/src/components/entrepreneur/EntrepreneurCard.tsx
index bd284146c..d1c13a2d6 100644
--- a/src/components/entrepreneur/EntrepreneurCard.tsx
+++ b/src/components/entrepreneur/EntrepreneurCard.tsx
@@ -49,7 +49,7 @@ export const EntrepreneurCard: React.FC = ({
{entrepreneur.industry}
- {entrepreneur.location}
+ {entrepreneur.teamSize}
Founded {entrepreneur.foundedYear}
diff --git a/src/components/investor/InvestorCard.tsx b/src/components/investor/InvestorCard.tsx
index 42bb0d59a..fa81f4277 100644
--- a/src/components/investor/InvestorCard.tsx
+++ b/src/components/investor/InvestorCard.tsx
@@ -19,12 +19,12 @@ export const InvestorCard: React.FC = ({
const navigate = useNavigate();
const handleViewProfile = () => {
- navigate(`/profile/investor/${investor.userId || investor._id}`);
+ navigate(`/profile/investor/${investor.userId}`);
};
const handleMessage = (e: React.MouseEvent) => {
e.stopPropagation(); // Prevent card click
- navigate(`/chat/${investor.userId || investor._id}`);
+ navigate(`/chat/${investor.userId}`);
};
return (
@@ -48,15 +48,16 @@ export const InvestorCard: React.FC = ({
{investor.name}
- Investor • {investor.totalInvestments} investments
+ Investor • {investor.totalInvestments || "0"} investments
- {investor.investmentStage && investor.investmentStage.map((stage, index) => (
-
- {stage}
-
- ))}
+ {investor.investmentStage &&
+ investor.investmentStage.map((stage, index) => (
+
+ {stage}
+
+ ))}
@@ -66,11 +67,12 @@ export const InvestorCard: React.FC = ({
Investment Interests
- {investor.investmentInterests && investor.investmentInterests.map((interest, index) => (
-
- {interest}
-
- ))}
+ {investor.investmentInterests &&
+ investor.investmentInterests.map((interest, index) => (
+
+ {interest}
+
+ ))}
diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx
index 201d5d6f3..c76cdf569 100644
--- a/src/components/layout/DashboardLayout.tsx
+++ b/src/components/layout/DashboardLayout.tsx
@@ -14,6 +14,8 @@ export const DashboardLayout: React.FC = () => {
const [incomingCall, setIncomingCall] = useState<{
from: string;
roomId: string;
+ callType: string;
+ fromName: string;
} | null>(null);
if (isLoading) {
@@ -30,7 +32,11 @@ export const DashboardLayout: React.FC = () => {
const acceptCall = () => {
if (incomingCall) {
socket?.emit("accept-call", { to: incomingCall.from });
- navigate(`/chat/${incomingCall.from}/audio-call/${incomingCall.roomId}`);
+ navigate(
+ `/chat/${incomingCall.from}/${incomingCall.callType}-call/${
+ incomingCall.roomId
+ }/${true}`
+ );
setIncomingCall(null);
}
};
@@ -43,8 +49,18 @@ export const DashboardLayout: React.FC = () => {
};
useEffect(() => {
- const handleIncoming = ({ from, roomId }: { from: string; roomId: string }) => {
- setIncomingCall({ from, roomId });
+ const handleIncoming = ({
+ from,
+ roomId,
+ callType,
+ fromName,
+ }: {
+ from: string;
+ roomId: string;
+ callType: string;
+ fromName: string;
+ }) => {
+ setIncomingCall({ from, roomId, callType, fromName });
};
socket?.on("incoming-call", handleIncoming);
@@ -55,11 +71,10 @@ export const DashboardLayout: React.FC = () => {
return (
-
{incomingCall && (
diff --git a/src/components/webrtc/IncomingCallModal.tsx b/src/components/webrtc/IncomingCallModal.tsx
index 788c0037d..f2ddec55a 100644
--- a/src/components/webrtc/IncomingCallModal.tsx
+++ b/src/components/webrtc/IncomingCallModal.tsx
@@ -2,18 +2,18 @@
import React from "react";
type Props = {
- from: string;
- roomId: string;
+ callType:string;
+ fromName:string;
onAccept: () => void;
onReject: () => void;
};
-const IncomingCallModal: React.FC
= ({ from, onAccept, onReject }) => {
+const IncomingCallModal: React.FC = ({ callType,fromName, onAccept, onReject }) => {
return (
-
+
📞 Incoming Call
-
User {from} is calling you
+
{fromName} request you for {callType} call
= ({
const token = localStorage.getItem("token");
if (token) {
axios
- .get(URL+"/auth/verify", {
+ .get(URL + "/auth/verify", {
headers: { Authorization: `Bearer ${token}` },
})
.then((res) => {
@@ -64,8 +64,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
);
const { token, user } = res.data;
+ console.log(user);
localStorage.setItem("token", token);
- setUser({...user,isOnline:true});
+ setUser({ ...user, isOnline: true });
toast.success("Successfully logged in!");
} catch (error) {
toast.error((error as Error).message);
@@ -112,14 +113,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
}
};
- // Mock forgot password function
+ // Forgot password function
const forgotPassword = async (email: string, role: string): Promise => {
try {
// Generate reset token (in a real app, this would be a secure token)
const resetToken = Math.random().toString(36).substring(2, 15);
localStorage.setItem(RESET_TOKEN_KEY, resetToken);
- // In a real app, this would send an email
+ // This would send an email
const resetLink = `https://nexus-gso984fz0-danish-ajmals-projects.vercel.app/reset-password?token=${resetToken}`;
const message = `
To reset your password, please click the button below:
@@ -143,6 +144,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
}
);
const { user } = res.data;
+
localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(user));
toast.success("Password reset instructions sent to your email");
} catch (error) {
@@ -161,15 +163,17 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
if (token !== storedToken) {
throw new Error("Invalid or expired reset token");
}
- const user = localStorage.getItem(USER_STORAGE_KEY);
+ const savedUser = localStorage.getItem(USER_STORAGE_KEY);
- await axios.patch(
- `${URL}/auth/update-password/${user._id}`,
- { newPassword, role: user.role },
+ const res = await axios.patch(
+ `${URL}/auth/update-password/${savedUser?.userId}`,
+ { newPassword },
{ withCredentials: true }
);
+ const { user } = res.data;
+ setUser(user);
- // In a real app, this would update the user's password in the database
+ // This would update the user's password in the database
localStorage.removeItem(RESET_TOKEN_KEY);
localStorage.removeItem("user");
toast.success("Password reset successfully");
@@ -199,12 +203,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
alert("Make changes to update profile..");
return;
}
+
const formData = new FormData();
formData.append("name", userData.name);
formData.append("location", userData.location);
formData.append("email", userData.email);
formData.append("bio", userData.bio);
formData.append("avatarUrl", userData.avatarUrl);
+
await axios
.post(`${URL}/user/update-profile/${userId}`, formData, {
withCredentials: true,
diff --git a/src/context/SocketContext.tsx b/src/context/SocketContext.tsx
index 678fd0966..0369976e0 100644
--- a/src/context/SocketContext.tsx
+++ b/src/context/SocketContext.tsx
@@ -8,7 +8,6 @@ type SocketContextType = {
};
const SocketContext = createContext({ socket: null });
-export const useSocket = () => useContext(SocketContext);
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
children,
@@ -47,3 +46,5 @@ export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
);
};
+export const useSocket = () => useContext(SocketContext);
+
diff --git a/src/data/messages.ts b/src/data/messages.ts
index 84c615125..3d5231ab9 100644
--- a/src/data/messages.ts
+++ b/src/data/messages.ts
@@ -1,5 +1,6 @@
import axios from "axios";
const URL = import.meta.env.VITE_BACKEND_URL;
+
// Helper function to get messages between two users
export const getMessagesBetweenUsers = async (
user1Id: string,
@@ -15,6 +16,7 @@ export const getMessagesBetweenUsers = async (
return messages;
};
+// save Messages btw users
export const saveMessagesBetweenUsers = async (newMessage: Any) => {
try {
const res = await axios.post(`${URL}/message/save-message`, newMessage, {
@@ -26,9 +28,10 @@ export const saveMessagesBetweenUsers = async (newMessage: Any) => {
console.log(err);
}
};
+
// Helper function to get conversations for a user
export const getConversationsForUser = async (
- currentUserId: string | undefined,
+ currentUserId: string | undefined
) => {
// Get unique conversation partners
const res = await axios.get(
@@ -41,20 +44,8 @@ export const getConversationsForUser = async (
return conversation;
};
-export const addConversationsForUser = async (con: object): any[] => {
- // Get unique conversation partners
- const res = await axios.post(
- `${URL}/conversation/add-conversations-for-user`,
- con,
- {
- withCredentials: true,
- }
- );
- const { conversationForSender } = res.data;
- return conversationForSender;
-};
-
-export const updateConversationsForUser = async (con: object): any[] => {
+// Update conversations dynamically
+export const updateConversationsForUser = async (con: object): any => {
// Get unique conversation partners
const res = await axios.post(
`${URL}/conversation/update-conversations-for-user`,
diff --git a/src/pages/chat/ChatPage.tsx b/src/pages/chat/ChatPage.tsx
index 582c99a9f..af11bdd9c 100644
--- a/src/pages/chat/ChatPage.tsx
+++ b/src/pages/chat/ChatPage.tsx
@@ -154,7 +154,6 @@ export const ChatPage: React.FC = () => {
return (
-
{/* Conversations sidebar */}
@@ -207,7 +206,7 @@ export const ChatPage: React.FC = () => {
`audio-call/${currentUser?.userId.slice(
0,
5
- )}&${chatPartner?._id.slice(0, 5)}`
+ )}&${chatPartner?._id.slice(0, 5)}/${false}`
);
}}
>
@@ -225,7 +224,7 @@ export const ChatPage: React.FC = () => {
`video-call/${currentUser?.userId.slice(
0,
5
- )}&${chatPartner?._id.slice(0, 5)}`
+ )}&${chatPartner?._id.slice(0, 5)}/${false}`
);
}}
>
diff --git a/src/pages/dashboard/EntrepreneurDashboard.tsx b/src/pages/dashboard/EntrepreneurDashboard.tsx
index ecfc6ef39..214fd6d6d 100644
--- a/src/pages/dashboard/EntrepreneurDashboard.tsx
+++ b/src/pages/dashboard/EntrepreneurDashboard.tsx
@@ -20,6 +20,7 @@ import { getInvestorsFromDb } from "../../data/users";
export const EntrepreneurDashboard: React.FC = () => {
const { user } = useAuth();
+ console.log(user)
const [collaborationRequests, setCollaborationRequests] = useState<
CollaborationRequest[]
>([]);
diff --git a/src/pages/investors/InvestorsPage.tsx b/src/pages/investors/InvestorsPage.tsx
index 30ef59387..345230158 100644
--- a/src/pages/investors/InvestorsPage.tsx
+++ b/src/pages/investors/InvestorsPage.tsx
@@ -6,7 +6,7 @@ import { Badge } from "../../components/ui/Badge";
import { InvestorCard } from "../../components/investor/InvestorCard";
import { useAuth } from "../../context/AuthContext";
import { getInvestorsFromDb } from "../../data/users";
-import { getRequestsForEntrepreneur } from "../../data/collaborationRequests";
+
import { Investor } from "../../types";
export const InvestorsPage: React.FC = () => {
diff --git a/src/pages/profile/InvestorProfile.tsx b/src/pages/profile/InvestorProfile.tsx
index 8f3480914..eb6928766 100644
--- a/src/pages/profile/InvestorProfile.tsx
+++ b/src/pages/profile/InvestorProfile.tsx
@@ -361,7 +361,7 @@ export const InvestorProfile: React.FC = () => {
- Investor • {investor.totalInvestments} investments
+ Investor • {investor.totalInvestments || "0"} investments
diff --git a/src/pages/webRTC/AudioCall.tsx b/src/pages/webRTC/AudioCall.tsx
index 0d7eac472..aa29d8a68 100644
--- a/src/pages/webRTC/AudioCall.tsx
+++ b/src/pages/webRTC/AudioCall.tsx
@@ -3,91 +3,138 @@ import { useNavigate, useParams } from "react-router-dom";
import { useAuth } from "../../context/AuthContext";
import { useSocket } from "../../context/SocketContext";
import toast from "react-hot-toast";
+import AgoraRTC, { ILocalTrack } from "agora-rtc-sdk-ng";
+import axios from "axios";
export const AudioCall: React.FC = () => {
- const { roomId, userId } = useParams();
+ const { roomId, userId, isIncommingCall } = useParams();
+ const [isMute, setIsMute] = useState
(false);
const { socket } = useSocket();
const { user } = useAuth();
-
- const localAudioRef = useRef(null);
- const remoteAudioRef = useRef(null);
- const pcRef = useRef(null);
const navigate = useNavigate();
-
+ const APP_ID = "5e3db4d74aaa43ff8eeee3ad9f08efd8";
+ const CHANNEL = String(roomId);
const [joined, setJoined] = useState(false);
- const [isMuted, setIsMuted] = useState(false);
+ const localAudioRef = useRef(null);
+ const remoteAudioRef = useRef(null);
+ const localTracksRef = useRef([]);
+ const clientRef = useRef(null);
+ const [token, setToken] = useState(null);
+ const [uid, setUid] = useState(null);
+ const URL = import.meta.env.VITE_BACKEND_URL;
useEffect(() => {
- pcRef.current = new RTCPeerConnection();
+ const fetchToken = async () => {
+ const uid = String(Date.now()); // simple unique uid
+ setUid(uid);
- // Handle remote stream
- pcRef.current.ontrack = (event) => {
- if (remoteAudioRef.current) {
- remoteAudioRef.current.srcObject = event.streams[0];
+ try {
+ const res = await axios.get(`${URL}/agora/rtc/${CHANNEL}/${uid}`, {
+ withCredentials: true,
+ });
+ setToken(res.data.token);
+ } catch (err) {
+ console.error("Failed to fetch token:", err);
}
};
- // ICE candidates → send to other peer
- pcRef.current.onicecandidate = (event) => {
- if (event.candidate) {
- socket?.emit("ice-candidate", { roomId, candidate: event.candidate });
- }
- };
+ fetchToken();
+ }, [CHANNEL, URL]);
- const stopStream = () => {
- const stream = localAudioRef.current?.srcObject as MediaStream;
- if (stream) {
- stream.getTracks().forEach((track) => {
- track.stop(); // stops both mic and camera
- });
- if (localAudioRef.current) {
- localAudioRef.current.srcObject = null;
+ useEffect(() => {
+ if (!token || !uid) return;
+
+ AgoraRTC.setLogLevel(0);
+
+ const init = async () => {
+ const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
+ clientRef.current = client;
+
+ // 🔹 Set up listeners BEFORE joining/publishing
+ client.on("user-published", async (user, mediaType) => {
+ if (user.uid === uid) return; // skip own stream
+ await client.subscribe(user, mediaType);
+ console.log("Subscribed to:", user.uid);
+
+ if (mediaType === "audio") {
+ user.audioTrack?.play();
}
- }
+ });
- // Close peer connection
- pcRef.current?.close();
- pcRef.current = null;
- socket?.emit("end-call", { to: userId, roomId });
- };
+ client.on("user-unpublished", (user, mediaType) => {
+ if (mediaType === "audio" && remoteAudioRef.current) {
+ remoteAudioRef.current.innerHTML = "";
+ }
+ });
- // ===== SOCKET LISTENERS =====
- socket?.on("offer", async ({ offer }) => {
try {
- await pcRef.current?.setRemoteDescription(
- new RTCSessionDescription(offer)
- );
- // Create answer
- const answer = await pcRef.current?.createAnswer();
- await pcRef.current?.setLocalDescription(answer);
- socket.emit("answer", { roomId, answer });
- } catch (err) {
- console.error("Error handling offer:", err);
- }
- });
+ await client.join(APP_ID, CHANNEL, token, uid);
- socket?.on("answer", async ({ answer }) => {
- try {
- if (pcRef.current && !pcRef.current.remoteDescription) {
- await pcRef.current.setRemoteDescription(
- new RTCSessionDescription(answer)
- );
- }
+ // create local tracks
+ localTracksRef.current = [await AgoraRTC.createMicrophoneAudioTrack()];
+
+ // publish AFTER listeners are ready
+ await client.publish(localTracksRef.current);
+
+ localTracksRef.current[0].play(localAudioRef.current!);
+ setJoined(true);
} catch (err) {
- console.error("Error setting remote description:", err);
+ console.error("Agora join error:", err);
}
- });
- socket?.on("ice-candidate", async ({ candidate }) => {
- try {
- if (pcRef.current?.remoteDescription) {
- await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
- }
- } catch (err) {
- console.error("Error adding ICE candidate:", err);
+ // Outgoing call
+ const isIncoming = isIncommingCall === "true";
+ if (!isIncoming) {
+ socket?.emit("start-call", {
+ from: user?.userId,
+ to: userId,
+ roomId: CHANNEL,
+ callType: "audio",
+ fromName: user?.name,
+ });
}
+ };
+
+ init();
+
+ return () => {
+ cleanup();
+ };
+ }, [CHANNEL, isIncommingCall, user, userId, token, uid, socket]);
+
+ // === Cleanup ===
+ const cleanup = async () => {
+ localTracksRef.current.forEach((track) => {
+ track.stop();
+ track.close();
});
+ if (clientRef.current) {
+ clientRef.current.removeAllListeners(); // ✅ avoid duplicate handlers
+ await clientRef.current.leave();
+ }
+ setJoined(false);
+ };
+
+ // Mute mic
+ const muteMic = async () => {
+ if (localTracksRef.current[0]) {
+ await localTracksRef.current[0].setEnabled(false);
+ setIsMute(true);
+ console.log("Microphone muted");
+ }
+ };
+
+ // Unmute mic
+ const unmuteMic = async () => {
+ if (localTracksRef.current[0]) {
+ await localTracksRef.current[0].setEnabled(true);
+
+ setIsMute(false);
+ console.log("Microphone unmuted");
+ }
+ };
+ useEffect(() => {
socket?.on("call-accepted", () => {
toast.success("Call accepted");
});
@@ -102,126 +149,87 @@ export const AudioCall: React.FC = () => {
navigate(`/chat/${userId}`);
});
- socket?.on("receiver-offline", () => {
+ socket?.on("receiver-offline", async () => {
toast.error("The receiver is offline.");
+ await cleanup();
navigate(`/chat/${userId}`);
});
- if (!joined) joinRoom();
-
return () => {
- socket?.off("offer");
- socket?.off("answer");
- socket?.off("ice-candidate");
socket?.off("call-accepted");
+ socket?.off("receiver-offline");
socket?.off("call-rejected");
socket?.off("call-ended");
};
- }, [roomId]);
-
- const joinRoom = async () => {
- setJoined(true);
- socket?.emit("join-room", { roomId });
-
- try {
- // Get media
- const stream = await navigator.mediaDevices.getUserMedia({
- audio: true,
- });
- if (localAudioRef.current) {
- localAudioRef.current.srcObject = stream;
- }
-
- // Add tracks
- if (pcRef.current?.getSenders().length === 0) {
- stream
- .getTracks()
- .forEach((track) => pcRef.current?.addTrack(track, stream));
- }
-
- // Caller creates offer
- if (user?.userId !== userId) {
- // Only caller
- const offer = await pcRef.current?.createOffer();
- await pcRef.current?.setLocalDescription(offer);
- socket?.emit("offer", { roomId, offer });
- socket?.emit("start-call", { from: user?.userId, to: userId, roomId });
- }
- } catch (error) {
- console.log("Audio call error:", error);
- }
- };
-
+ }, [socket, userId]);
return (
-
- {/* Audio Area */}
-
- {/* Local Audio */}
-
-
-
- You
-
+
+
Agora 1-to-1 Audio Call
+
+
+
+
+
+ P
+
+
+ {/* Local audio as overlay (picture-in-picture) */}
+
+
+ U
+
+
+
-
- {/* Remote Audio */}
-
-
-
-
- Partner
-
+
+ {joined ? (
+
📡 Call in Progress...
+ ) : (
+
Not connected
+ )}
+
+ {/* End Call */}
+ {
+ e.preventDefault();
+ cleanup();
+ navigate(`/chat/${userId}`);
+ socket?.emit("end-call", { to: userId, roomId: CHANNEL });
+ }}
+ className="rounded-full p-3 bg-red-600 text-white"
+ >
+ 📞
+
+
+ {/* Mic Control */}
+ {isMute ? (
+ {
+ e.preventDefault();
+ await unmuteMic();
+ }}
+ className="rounded-full p-3 bg-green-600 text-white"
+ >
+ 🎤
+
+ ) : (
+
+ 🔇
+
+ )}
+
-
- {/* Control Bar */}
-
- {
- e.preventDefault();
-
- const stream = localAudioRef.current?.srcObject as MediaStream;
- if (stream) {
- // Stop all tracks (camera + mic)
- stream.getTracks().forEach((track) => {
- track.stop();
- });
-
- // Clear local audio element
- if (localAudioRef.current) {
- localAudioRef.current.srcObject = null;
- }
- }
-
- // Close peer connection
- if (pcRef.current) {
- pcRef.current
- .getSenders()
- .forEach((sender) => sender.track?.stop());
- pcRef.current.close();
- pcRef.current = null;
- }
-
- // Also clear remote audio element (so frozen audio doesn’t stay visible)
- if (remoteAudioRef.current) {
- remoteAudioRef.current.srcObject = null;
- }
-
- // Tell server call ended
- socket?.emit("end-call", { to: userId, roomId });
- navigate(`/chat/${userId}`);
- }}
- >
- 📞
-
-
);
};
diff --git a/src/pages/webRTC/Videocall.tsx b/src/pages/webRTC/Videocall.tsx
index e66d086f8..cb1414c6f 100644
--- a/src/pages/webRTC/Videocall.tsx
+++ b/src/pages/webRTC/Videocall.tsx
@@ -3,91 +3,164 @@ import { useNavigate, useParams } from "react-router-dom";
import { useAuth } from "../../context/AuthContext";
import { useSocket } from "../../context/SocketContext";
import toast from "react-hot-toast";
+import AgoraRTC, { ILocalTrack } from "agora-rtc-sdk-ng";
+import axios from "axios";
export const VideoCall: React.FC = () => {
- const { roomId, userId } = useParams();
+ const { roomId, userId, isIncommingCall } = useParams();
+ const [isMute, setIsMute] = useState
(false);
+ const [isVideoOn, setIsVideoOn] = useState(false);
const { socket } = useSocket();
const { user } = useAuth();
-
- const localVideoRef = useRef(null);
- const remoteVideoRef = useRef(null);
- const pcRef = useRef(null);
const navigate = useNavigate();
-
+ const APP_ID = "5e3db4d74aaa43ff8eeee3ad9f08efd8";
+ const CHANNEL = String(roomId);
const [joined, setJoined] = useState(false);
- const [isMuted, setIsMuted] = useState(false);
+ const localVideoRef = useRef(null);
+ const remoteVideoRef = useRef(null);
+ const localTracksRef = useRef([]);
+ const clientRef = useRef(null);
+ const [token, setToken] = useState(null);
+ const [uid, setUid] = useState(null);
+ const URL = import.meta.env.VITE_BACKEND_URL;
useEffect(() => {
- pcRef.current = new RTCPeerConnection();
+ const fetchToken = async () => {
+ // generate uid (string or number)
+ const uid = String(Date.now()); // simple unique uid
+ setUid(uid);
- // Handle remote stream
- pcRef.current.ontrack = (event) => {
- if (remoteVideoRef.current) {
- remoteVideoRef.current.srcObject = event.streams[0];
+ try {
+ const res = await axios.get(`${URL}/agora/rtc/${CHANNEL}/${uid}`, {
+ withCredentials: true,
+ });
+ setToken(res.data.token);
+ } catch (err) {
+ console.error("Failed to fetch token:", err);
}
};
- // ICE candidates → send to other peer
- pcRef.current.onicecandidate = (event) => {
- if (event.candidate) {
- socket?.emit("ice-candidate", { roomId, candidate: event.candidate });
- }
- };
+ fetchToken();
+ }, [CHANNEL, URL]);
- const stopStream = () => {
- const stream = localVideoRef.current?.srcObject as MediaStream;
- if (stream) {
- stream.getTracks().forEach((track) => {
- track.stop(); // stops both mic and camera
- });
- if (localVideoRef.current) {
- localVideoRef.current.srcObject = null;
+ useEffect(() => {
+ if (!token || !uid) return;
+
+ AgoraRTC.setLogLevel(0);
+
+ const init = async () => {
+ const client = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });
+ clientRef.current = client;
+
+ // 🔹 Set up listeners BEFORE joining/publishing
+ client.on("user-published", async (user, mediaType) => {
+ if (user.uid === uid) return; // skip own stream
+ await client.subscribe(user, mediaType);
+ console.log("Subscribed to:", user.uid);
+
+ if (mediaType === "video") {
+ user.videoTrack?.play(remoteVideoRef.current!);
}
- }
+ if (mediaType === "audio") {
+ user.audioTrack?.play();
+ }
+ });
- // Close peer connection
- pcRef.current?.close();
- pcRef.current = null;
- socket?.emit("end-call", { to: userId, roomId });
- };
+ client.on("user-unpublished", (user, mediaType) => {
+ if (mediaType === "video" && remoteVideoRef.current) {
+ remoteVideoRef.current.innerHTML = "";
+ }
+ });
- // ===== SOCKET LISTENERS =====
- socket?.on("offer", async ({ offer }) => {
try {
- await pcRef.current?.setRemoteDescription(
- new RTCSessionDescription(offer)
- );
- // Create answer
- const answer = await pcRef.current?.createAnswer();
- await pcRef.current?.setLocalDescription(answer);
- socket.emit("answer", { roomId, answer });
- } catch (err) {
- console.error("Error handling offer:", err);
- }
- });
+ await client.join(APP_ID, CHANNEL, token, uid);
- socket?.on("answer", async ({ answer }) => {
- try {
- if (pcRef.current && !pcRef.current.remoteDescription) {
- await pcRef.current.setRemoteDescription(
- new RTCSessionDescription(answer)
- );
- }
+ // create local tracks
+ localTracksRef.current =
+ await AgoraRTC.createMicrophoneAndCameraTracks();
+
+ // publish AFTER listeners are ready
+ await client.publish(localTracksRef.current);
+
+ localTracksRef.current[1].play(localVideoRef.current!);
+ setIsVideoOn(true);
+ setJoined(true);
} catch (err) {
- console.error("Error setting remote description:", err);
+ console.error("Agora join error:", err);
}
- });
- socket?.on("ice-candidate", async ({ candidate }) => {
- try {
- if (pcRef.current?.remoteDescription) {
- await pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
- }
- } catch (err) {
- console.error("Error adding ICE candidate:", err);
+ // Outgoing call
+ const isIncoming = isIncommingCall === "true";
+ if (!isIncoming) {
+ socket?.emit("start-call", {
+ from: user?.userId,
+ to: userId,
+ roomId: CHANNEL,
+ callType:"video",
+ fromName:user?.name
+ });
}
+ };
+
+ init();
+
+ return () => {
+ cleanup();
+ };
+ }, [CHANNEL, isIncommingCall, user, userId, token, uid, socket]);
+
+ // === Cleanup ===
+ const cleanup = async () => {
+ localTracksRef.current.forEach((track) => {
+ track.stop();
+ track.close();
});
+ if (clientRef.current) {
+ clientRef.current.removeAllListeners(); // ✅ avoid duplicate handlers
+ await clientRef.current.leave();
+ }
+ setJoined(false);
+ };
+
+ // Mute mic
+ const muteMic = async () => {
+ if (localTracksRef.current[0]) {
+ await localTracksRef.current[0].setEnabled(false);
+ setIsMute(true);
+ console.log("Microphone muted");
+ }
+ };
+
+ // Unmute mic
+ const unmuteMic = async () => {
+ if (localTracksRef.current[0]) {
+ await localTracksRef.current[0].setEnabled(true);
+
+ setIsMute(false);
+ console.log("Microphone unmuted");
+ }
+ };
+
+ // Stop video
+ const stopVideo = async () => {
+ if (localTracksRef.current[1]) {
+ await localTracksRef.current[1].setEnabled(false);
+
+ setIsVideoOn(false);
+ console.log("Video stopped");
+ }
+ };
+
+ // Resume video
+ const resumeVideo = async () => {
+ if (localTracksRef.current[1]) {
+ await localTracksRef.current[1].setEnabled(true);
+ setIsVideoOn(true);
+ console.log("Video resumed");
+ }
+ };
+ useEffect(() => {
socket?.on("call-accepted", () => {
toast.success("Call accepted");
});
@@ -102,132 +175,108 @@ export const VideoCall: React.FC = () => {
navigate(`/chat/${userId}`);
});
- socket?.on("receiver-offline", () => {
+ socket?.on("receiver-offline", async () => {
toast.error("The receiver is offline.");
+ await cleanup();
navigate(`/chat/${userId}`);
});
- if (!joined) joinRoom();
-
return () => {
- socket?.off("offer");
- socket?.off("answer");
- socket?.off("ice-candidate");
socket?.off("call-accepted");
+ socket?.off("receiver-offline");
socket?.off("call-rejected");
socket?.off("call-ended");
};
- }, [roomId]);
-
- const joinRoom = async () => {
- setJoined(true);
- socket?.emit("join-room", { roomId });
-
- try {
- // Get media
- const stream = await navigator.mediaDevices.getUserMedia({
- video: true,
- audio: true,
- });
- if (localVideoRef.current) {
- localVideoRef.current.srcObject = stream;
- }
-
- // Add tracks
- if (pcRef.current?.getSenders().length === 0) {
- stream
- .getTracks()
- .forEach((track) => pcRef.current?.addTrack(track, stream));
- }
-
- // Caller creates offer
- if (user?.userId !== userId) {
- // Only caller
- const offer = await pcRef.current?.createOffer();
- await pcRef.current?.setLocalDescription(offer);
- socket?.emit("offer", { roomId, offer });
- socket?.emit("start-call", { from: user?.userId, to: userId, roomId });
- }
- } catch (error) {
- console.log("Video call error:", error);
- }
- };
-
+ }, [socket, userId]);
return (
-
- {/* Video Area */}
-
- {/* Local Video */}
-
-
-
- You
-
+
+
Agora 1-to-1 Video Call
+
+
+
+
+ {remoteVideoRef.current === null && (
+
+ O
+
+ )}
+
+ {/* Local video as overlay (picture-in-picture) */}
+
+
+ {!isVideoOn && (
+
+ Q
+
+ )}
+
+
- {/* Remote Video */}
-
-
-
-
+
+ {joined ? (
+
📡 Call in Progress...
+ ) : (
+
Not connected
+ )}
+
+ {/* End Call */}
+
{
+ e.preventDefault();
+ cleanup();
+ navigate(`/chat/${userId}`);
+ socket?.emit("end-call", { to: userId, roomId: CHANNEL });
+ }}
+ className="rounded-full p-3 bg-red-600 text-white"
+ >
+ 📞
+
- {/* Control Bar */}
-
- {
- e.preventDefault();
-
- const stream = localVideoRef.current?.srcObject as MediaStream;
- if (stream) {
- // Stop all tracks (camera + mic)
- stream.getTracks().forEach((track) => {
- track.stop();
- });
-
- // Clear local video element
- if (localVideoRef.current) {
- localVideoRef.current.srcObject = null;
- }
- }
-
- // Close peer connection
- if (pcRef.current) {
- pcRef.current
- .getSenders()
- .forEach((sender) => sender.track?.stop());
- pcRef.current.close();
- pcRef.current = null;
- }
-
- // Also clear remote video element (so frozen video doesn’t stay visible)
- if (remoteVideoRef.current) {
- remoteVideoRef.current.srcObject = null;
- }
-
- // Tell server call ended
- socket?.emit("end-call", { to: userId, roomId });
- navigate(`/chat/${userId}`);
- }}
- >
- 📞
-
+ {/* Mic Control */}
+ {isMute ? (
+ {
+ e.preventDefault();
+ await unmuteMic();
+ }}
+ className="rounded-full p-3 bg-green-600 text-white"
+ >
+ 🎤
+
+ ) : (
+
+ 🔇
+
+ )}
+
+ {/* Video Control */}
+ {isVideoOn ? (
+
+ 📷
+
+ ) : (
+
+ 📹
+
+ )}
+
+
);
From aac75dd126b8492b547841081d92e6b027171dd7 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Tue, 7 Oct 2025 21:43:00 +0500
Subject: [PATCH 22/49] OAuth in progress
---
src/pages/webRTC/AudioCall.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/pages/webRTC/AudioCall.tsx b/src/pages/webRTC/AudioCall.tsx
index aa29d8a68..beb701a75 100644
--- a/src/pages/webRTC/AudioCall.tsx
+++ b/src/pages/webRTC/AudioCall.tsx
@@ -12,7 +12,7 @@ export const AudioCall: React.FC = () => {
const { socket } = useSocket();
const { user } = useAuth();
const navigate = useNavigate();
- const APP_ID = "5e3db4d74aaa43ff8eeee3ad9f08efd8";
+ const APP_ID = import.meta.env.VITE_APP_ID;
const CHANNEL = String(roomId);
const [joined, setJoined] = useState(false);
const localAudioRef = useRef(null);
@@ -25,7 +25,7 @@ export const AudioCall: React.FC = () => {
useEffect(() => {
const fetchToken = async () => {
- const uid = String(Date.now()); // simple unique uid
+ const uid = String(Date.now());
setUid(uid);
try {
@@ -100,7 +100,7 @@ export const AudioCall: React.FC = () => {
return () => {
cleanup();
};
- }, [CHANNEL, isIncommingCall, user, userId, token, uid, socket]);
+ }, [CHANNEL, isIncommingCall,APP_ID, user, userId, token, uid, socket]);
// === Cleanup ===
const cleanup = async () => {
@@ -161,7 +161,7 @@ export const AudioCall: React.FC = () => {
socket?.off("call-rejected");
socket?.off("call-ended");
};
- }, [socket, userId]);
+ }, [socket, userId,navigate]);
return (
Agora 1-to-1 Audio Call
From b543396a39279962e4372d1a61a898d756312868 Mon Sep 17 00:00:00 2001
From: Danish-Butt
Date: Sat, 11 Oct 2025 21:23:46 +0500
Subject: [PATCH 23/49] linkedin and google oAuth integrated
---
public/app logo.jpeg | Bin 0 -> 62816 bytes
src/App.tsx | 5 +-
src/context/AuthContext.tsx | 33 +++++
src/pages/auth/LoginPage.tsx | 178 ++++++++++++++++++--------
src/pages/auth/LoginWithOAuthPage.tsx | 116 +++++++++++++++++
src/types/index.ts | 21 +--
6 files changed, 291 insertions(+), 62 deletions(-)
create mode 100644 public/app logo.jpeg
create mode 100644 src/pages/auth/LoginWithOAuthPage.tsx
diff --git a/public/app logo.jpeg b/public/app logo.jpeg
new file mode 100644
index 0000000000000000000000000000000000000000..9f3abdca7b91217b22b81d18c768948a9648f3bb
GIT binary patch
literal 62816
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