Skip to content

Commit 35765d1

Browse files
committed
Merge branch 'feature/Cambios' into fix/refactor-rec-profile-card
2 parents 832e608 + a7d68e0 commit 35765d1

File tree

9 files changed

+185
-153
lines changed

9 files changed

+185
-153
lines changed

src/features/developer/components/ProjectInfoCard.jsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import { AvatarImage } from "../../../components/AvatarImage";
1515
import { NameUsers } from "../../../components/NameUsers";
1616

17-
export const ProjectInfoCard = ({ project }) => {
17+
export const ProjectInfoCard = ({ project, setSelectedOwner }) => {
1818
const [showAllSkills, setShowAllSkills] = useState(false);
1919

2020
if (!project) return null;
@@ -30,9 +30,9 @@ export const ProjectInfoCard = ({ project }) => {
3030
owner,
3131
} = project;
3232

33-
const skillsToShow = showAllSkills
34-
? projectSkills
35-
: projectSkills?.slice(0, 5) || [];
33+
setSelectedOwner(owner);
34+
35+
const skillsToShow = showAllSkills ? projectSkills : projectSkills?.slice(0, 5) || [];
3636

3737
console.log("owner en ProjectInfoCard:", owner);
3838

@@ -153,9 +153,7 @@ export const ProjectInfoCard = ({ project }) => {
153153
type="button"
154154
onClick={() => setShowAllSkills(true)}
155155
className="bg-neutral-60 text-neutral-30 px-2 py-0.5 rounded-full text-sm cursor-pointer"
156-
aria-label={`Show ${
157-
projectSkills.length - 5
158-
} more skills`}
156+
aria-label={`Show ${projectSkills.length - 5} more skills`}
159157
>
160158
+{projectSkills.length - 5}
161159
</button>

src/features/developer/pages/ProjectDetailsPage.jsx

Lines changed: 60 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useContext, useEffect, useState } from "react";
22
import { useParams } from "react-router";
33
import { getProjectById, incrementProjectView, toggleProjectLike, getProjectLikeStatus } from "../../../services/projectService";
44
import { ProjectInfoCard } from "../components/ProjectInfoCard";
55
import { GitHubFileTree } from "../components/GitHubFileTree";
66
import { GitHubLanguagesTag } from "../components/GitHubLanguagesTag";
77
import { CodeBlock } from "../../../styles/ReactParser";
88
import { LikeButtonRounded } from "../../../components/LikeButtonRounded";
9+
import { AuthContext } from "../../../context/authContext";
10+
911

1012
function useWindowWidth() {
1113
const [width, setWidth] = useState(window.innerWidth);
@@ -34,7 +36,8 @@ export const ProjectDetailsPage = () => {
3436
const [currentIndex, setCurrentIndex] = useState(0);
3537
const width = useWindowWidth();
3638
const isDesktop = width >= 1200;
37-
39+
const { profile, socket, setNotifications } = useContext(AuthContext);
40+
const [selectedOwner, setSelectedOwner] = useState(null);
3841
const token = localStorage.getItem('token') || '';
3942

4043
// Estados del botón de like
@@ -43,46 +46,46 @@ export const ProjectDetailsPage = () => {
4346
const [loadingLike, setLoadingLike] = useState(true);
4447

4548
useEffect(() => {
46-
const incrementViewWithCooldown = async () => {
47-
if (typeof window === "undefined" || !window.localStorage) return;
48-
49-
const cooldownTime = 1000 * 60 * 60; // 1 hora en ms
50-
const lastViewKey = `project_${id}_last_view`;
51-
const lastView = localStorage.getItem(lastViewKey);
52-
const now = Date.now();
49+
const incrementViewWithCooldown = async () => {
50+
if (typeof window === "undefined" || !window.localStorage) return;
51+
52+
const cooldownTime = 1000 * 60 * 60; // 1 hora en ms
53+
const lastViewKey = `project_${id}_last_view`;
54+
const lastView = localStorage.getItem(lastViewKey);
55+
const now = Date.now();
56+
57+
if (!lastView || now - parseInt(lastView, 10) > cooldownTime) {
58+
try {
59+
await incrementProjectView(id);
60+
localStorage.setItem(lastViewKey, now.toString());
61+
62+
// Actualizar localmente el contador de views para reflejar el cambio instantáneamente:
63+
setProject(prev => prev ? { ...prev, views: (prev.views || 0) + 1 } : prev);
64+
} catch (error) {
65+
console.error("Error incrementando views:", error);
66+
}
67+
}
68+
};
5369

54-
if (!lastView || now - parseInt(lastView, 10) > cooldownTime) {
70+
const fetchProjectAndLikeStatus = async () => {
5571
try {
56-
await incrementProjectView(id);
57-
localStorage.setItem(lastViewKey, now.toString());
58-
59-
// Actualizar localmente el contador de views para reflejar el cambio instantáneamente:
60-
setProject(prev => prev ? { ...prev, views: (prev.views || 0) + 1 } : prev);
72+
const data = await getProjectById(id);
73+
if (data && !data.error) {
74+
setProject(data);
75+
setCurrentIndex(0);
76+
}
77+
const likeStatus = await getProjectLikeStatus(id, token);
78+
setLiked(likeStatus.liked);
6179
} catch (error) {
62-
console.error("Error incrementando views:", error);
63-
}
64-
}
65-
};
66-
67-
const fetchProjectAndLikeStatus = async () => {
68-
try {
69-
const data = await getProjectById(id);
70-
if (data && !data.error) {
71-
setProject(data);
72-
setCurrentIndex(0);
80+
console.error("Error fetching project or like status:", error);
81+
} finally {
82+
setLoadingLike(false);
7383
}
74-
const likeStatus = await getProjectLikeStatus(id, token);
75-
setLiked(likeStatus.liked);
76-
} catch (error) {
77-
console.error("Error fetching project or like status:", error);
78-
} finally {
79-
setLoadingLike(false);
80-
}
81-
};
84+
};
8285

83-
fetchProjectAndLikeStatus();
84-
incrementViewWithCooldown();
85-
}, [id, token]);
86+
fetchProjectAndLikeStatus();
87+
incrementViewWithCooldown();
88+
}, [id, token]);
8689

8790

8891
// Función para manejar el click del like
@@ -93,12 +96,24 @@ export const ProjectDetailsPage = () => {
9396
try {
9497
const data = await toggleProjectLike(id, token);
9598
setLiked(data.liked);
96-
// Actualizamos el proyecto con la info de likes para que refleje contador, estado, etc.
9799
setProject((prevProject) => ({
98100
...prevProject,
99101
liked: data.liked,
100102
likes: data.likes,
101103
}));
104+
105+
if (data.liked && selectedOwner._id && selectedOwner._id !== profile._id) {
106+
const notif = {
107+
senderId: profile._id,
108+
senderName: profile.name,
109+
receiverId: selectedOwner._id,
110+
receiverName: selectedOwner.name,
111+
type: 2,
112+
createdAt: Date.now()
113+
};
114+
socket.emit("sendNotification", notif);
115+
setNotifications(prev => [notif, ...prev]);
116+
}
102117
} catch (error) {
103118
console.error(error);
104119
} finally {
@@ -133,7 +148,7 @@ export const ProjectDetailsPage = () => {
133148
? parseRepoUrl(project.githubProjectLink)
134149
: null;
135150

136-
151+
137152
return (
138153
<div className="w-full flex flex-col items-center gap-6">
139154
<div className="relative w-full h-[256px] overflow-hidden">
@@ -164,9 +179,8 @@ export const ProjectDetailsPage = () => {
164179
key={index}
165180
src={url}
166181
alt={`slide-${index}`}
167-
className={`absolute inset-0 w-full h-full object-cover rounded-lg transition-opacity duration-700 ease-in-out ${
168-
index === currentIndex ? "opacity-100 z-10" : "opacity-0 z-0"
169-
}`}
182+
className={`absolute inset-0 w-full h-full object-cover rounded-lg transition-opacity duration-700 ease-in-out ${index === currentIndex ? "opacity-100 z-10" : "opacity-0 z-0"
183+
}`}
170184
/>
171185
))}
172186
<button
@@ -208,7 +222,7 @@ export const ProjectDetailsPage = () => {
208222
</div>
209223

210224
<div className="flex flex-col">
211-
<ProjectInfoCard project={project} />
225+
<ProjectInfoCard project={project} setSelectedOwner={setSelectedOwner} />
212226

213227
<div className="flex justify-center items-center rounded-full p-6">
214228
<LikeButtonRounded
@@ -251,9 +265,8 @@ export const ProjectDetailsPage = () => {
251265
key={index}
252266
src={url}
253267
alt={`slide-${index}`}
254-
className={`absolute inset-0 w-full h-full object-cover rounded-lg transition-opacity duration-700 ease-in-out ${
255-
index === currentIndex ? "opacity-100 z-10" : "opacity-0 z-0"
256-
}`}
268+
className={`absolute inset-0 w-full h-full object-cover rounded-lg transition-opacity duration-700 ease-in-out ${index === currentIndex ? "opacity-100 z-10" : "opacity-0 z-0"
269+
}`}
257270
/>
258271
))}
259272
<button
@@ -273,7 +286,7 @@ export const ProjectDetailsPage = () => {
273286
);
274287

275288
case "ProjectInfoCard":
276-
return <ProjectInfoCard key="infoCard" project={project} />;
289+
return <ProjectInfoCard key="infoCard" project={project} setSelectedOwner={setSelectedOwner} />;
277290

278291
case "Description":
279292
return project.description ? (

src/features/recruiters/components/OfferModal.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,18 +71,18 @@ export const OfferModal = ({ token, reloadPage, idOffer, isOpen, setIsOpen, oper
7171
return (
7272
// Overlay y centrar modal
7373
<dialog open={isOpen} className="modal">
74-
<div className="inset-0 flex items-center justify-center">
74+
<div className="fixed inset-0 flex items-center justify-center z-50 bg-black/40">
7575
{/* Caja del modal centrada */}
76-
<div className="modal-box max-w-3xl w-full bg-neutral-80 border border-neutral-70 text-neutral-0 shadow-md rounded-lg my-auto self-center">
76+
<div className="modal-box w-[95vw] max-w-full md:max-w-3xl bg-neutral-80 border border-neutral-70 text-neutral-0 shadow-md rounded-lg p-0">
7777
<form
7878
method="dialog"
7979
onSubmit={handleSubmit(onSubmit)}
8080
className="space-y-6 p-4 sm:p-6"
8181
>
82-
<h2 className="text-3xl font-bold text-center mb-4">Post a Job Offer</h2>
82+
<h2 className="text-2xl sm:text-3xl font-bold text-center mb-4">Post a Job Offer</h2>
8383
<hr className="border-t border-neutral-60" />
8484

85-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 pt-2">
85+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 pt-2">
8686
{/* Position */}
8787
<div>
8888
<label className="block text-sm text-neutral-20 mb-1">Position</label>
@@ -189,7 +189,7 @@ export const OfferModal = ({ token, reloadPage, idOffer, isOpen, setIsOpen, oper
189189
</div>
190190

191191
<div className="flex flex-col md:flex-row justify-end gap-4 pt-4">
192-
<button type="submit" className="btn bg-primary-60 text-neutral-90 hover:bg-primary-70 w-full md:w-auto">
192+
<button type="submit" className="btn bg-primary-60 hover:bg-primary-70 w-full md:w-auto">
193193
{operacion !== 'crear' ? "Edit Offer" : "Create Offer"}
194194
</button>
195195
<button

src/layout/Header.jsx

Lines changed: 5 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,16 @@ import { SideMenu } from "./components/SideMenu";
55
import { Navbar } from "./components/Navbar";
66
import { AuthMenu } from "./components/AuthMenu";
77
import { useState } from "react";
8-
import { CiBellOn } from "react-icons/ci";
9-
import { formatMessageTime } from "../utils/utils";
8+
import { Notifications } from "./components/Notifications";
109

1110

1211
export const Header = () => {
13-
const { profile, logout, notifications, setNotifications } = useContext(AuthContext);
12+
const { profile, logout } = useContext(AuthContext);
1413
const [openSideMenu, setOpenSideMenu] = useState(false);
15-
const [notificationsOpen, setNotificationsOpen] = useState(false);
1614
const toggleSideMenu = () => {
1715
setOpenSideMenu(!openSideMenu);
1816
};
1917

20-
const getNotificationText = (notif) => {
21-
if (notif.type === 1) return null;
22-
const types = {
23-
2: `${notif.senderName} ha dado like a tu Proyecto`,
24-
3: `${notif.senderName} ha comentado en tu foto`,
25-
// añade más tipos según necesites
26-
};
27-
return types[notif.type] || "Tienes una nueva notificación";
28-
};
29-
3018
return (
3119
<header className='bg-neutral-80 py-2 pl-2 drawer border-b-1 border-neutral-70'>
3220
<input
@@ -41,67 +29,9 @@ export const Header = () => {
4129
<Navbar />
4230

4331
{/* Bell icon y menú de notificaciones */}
44-
<div className="mx-4 flex items-center">
45-
<div className="relative">
46-
{
47-
profile && (
48-
<button
49-
type="button"
50-
className="relative flex items-center justify-center text-white hover:text-gray-200"
51-
aria-label="Notificaciones"
52-
onClick={() => setNotificationsOpen((prev) => !prev)}
53-
>
54-
<CiBellOn className="h-6 w-6" />
55-
{notifications.filter(notif => notif.type !== 1).length > 0 && (
56-
<span className="absolute -top-1 -right-1 h-4 w-4 bg-red-500 text-white text-xs rounded-full flex items-center justify-center">
57-
{notifications.filter(notif => notif.type !== 1).length}
58-
</span>
59-
)}
60-
</button>
61-
)
62-
}
63-
64-
{/* Panel de notificaciones */}
65-
{profile && notificationsOpen && (
66-
<div className="absolute right-0 mt-2 w-80 max-w-xs bg-neutral-70 shadow-lg rounded-xl z-50">
67-
<div className="flex justify-between items-center px-4 py-3 border-b border-base-200">
68-
<span className="font-semibold text-base-content">Notificaciones</span>
69-
<button
70-
onClick={() => setNotificationsOpen(false)}
71-
className="text-base-content hover:text-error text-xl"
72-
aria-label="Cerrar"
73-
>
74-
×
75-
</button>
76-
</div>
77-
78-
<div className="max-h-72 overflow-y-auto p-2 space-y-2">
79-
{notifications.filter(notif => notif.type !== 1).length > 0 ? (
80-
notifications
81-
.filter(notif => notif.type !== 1)
82-
.map((notif, index) => (
83-
<div
84-
key={notif._id || `${notif.senderName}-${notif.type}-${index}`}
85-
className="w-full bg-neutral-60 text-base-content shadow-md rounded-md p-3"
86-
>
87-
<div className="text-sm font-medium">
88-
{getNotificationText(notif)}
89-
</div>
90-
<span className="text-xs opacity-60 block mt-1">
91-
{formatMessageTime(notif.createdAt)}
92-
</span>
93-
</div>
94-
))
95-
) : (
96-
<div className="p-3 text-sm text-gray-400 text-center">
97-
No hay notificaciones
98-
</div>
99-
)}
100-
</div>
101-
</div>
102-
)}
103-
</div>
104-
</div>
32+
{/* <div className="mx-4 flex items-center">
33+
<Notifications/>
34+
</div> */}
10535

10636
{/* Menu User */}
10737
<AuthMenu profile={profile} logout={logout} notifications={notifications} setNotifications={setNotifications} setNotificationsOpen={setNotificationsOpen} getNotificationText={getNotificationText} />

src/layout/chat/Widget.jsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import { ChatContext } from "./context/ChatContext";
66

77
export function Widget() {
88
// const [isOpen, setIsOpen] = useState(false);
9-
const { profile, token } = useContext(AuthContext);
10-
const {toggleChat, isOpen} = useContext(ChatContext);
9+
const { profile, token, notifications } = useContext(AuthContext);
10+
const { toggleChat, isOpen } = useContext(ChatContext);
1111

1212
// const toggleChat = () => {
1313
// setIsOpen(!isOpen);
1414
// };
1515

16+
const unreadChats = notifications.filter(n => n.type === 1).length;
17+
1618
return (
1719
<>
1820
{token && (
@@ -28,7 +30,7 @@ export function Widget() {
2830
${isOpen ? 'scale-100 opacity-100' : 'scale-0 opacity-0 pointer-events-none'}
2931
`}
3032
style={{
31-
boxShadow: '0 50px 50px rgba(0, 0, 0, 0.8)' // más profunda
33+
boxShadow: '0 50px 50px rgba(0, 0, 0, 0.8)'
3234
}}
3335
>
3436
<ChatPanel onClose={toggleChat} user={profile} />
@@ -42,9 +44,14 @@ export function Widget() {
4244
}}
4345
className="
4446
w-14 h-14 rounded-full text-white flex items-center justify-center shadow-2xl hover:brightness-90
45-
transition transform active:scale-90 duration-150 ease-out"
47+
transition transform active:scale-90 duration-150 ease-out relative"
4648
>
4749
<PiChat size={28} />
50+
{unreadChats > 0 && (
51+
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs px-2 py-0.5 rounded-full animate-pulse min-w-[22px] text-center">
52+
{unreadChats}
53+
</span>
54+
)}
4855
</button>
4956
</div>
5057
</div>

0 commit comments

Comments
 (0)