Skip to content

Commit ea84b54

Browse files
authored
Merge pull request #47 from FSDSTR0225/dev
Dev
2 parents 3a23ce1 + 8376e18 commit ea84b54

File tree

19 files changed

+967
-429
lines changed

19 files changed

+967
-429
lines changed

src/components/AvatarImage.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const [imgError, setImgError] = useState(null);
4545
return (
4646
< >
4747
{( user?.avatar && !imgError) ? (
48-
<div className={`avatar outline-2 outline-neutral-90 rounded-full ${userOnline && (isDeveloper ? 'avatar-online before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] before:bg-primary-50 outline-primary-50 ' : 'before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] outline-secondary-50 avatar-online before:bg-secondary-50')}`}>
48+
<div className={`avatar outline-2 outline-neutral-60 rounded-full ${userOnline && (isDeveloper ? 'avatar-online before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] before:bg-primary-50 outline-primary-50 ' : 'before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] outline-secondary-50 avatar-online before:bg-secondary-50')}`}>
4949
<div className={`rounded-full ${sizeClass} `}>
5050
<img
5151
src={ user?.avatar}
@@ -55,7 +55,7 @@ const [imgError, setImgError] = useState(null);
5555
</div>
5656
</div>
5757
) : (
58-
<div className={`avatar avatar-placeholder outline-2 outline-neutral-90 rounded-full ${userOnline && (isDeveloper ? 'avatar-online before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] before:bg-primary-50 outline-primary-50 ' : 'before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] outline-secondary-50 avatar-online before:bg-secondary-50')}`}>
58+
<div className={`avatar avatar-placeholder outline-2 outline-neutral-60 rounded-full ${userOnline && (isDeveloper ? 'avatar-online before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] before:bg-primary-50 outline-primary-50 ' : 'before:w-[20%] before:h-[20%] before:top-[6%] before:right-[2%] outline-secondary-50 avatar-online before:bg-secondary-50')}`}>
5959
<div className={`bg-neutral text-neutral-content rounded-full ${sizeClass}`}>
6060
<span className=' font-bold'>{getInitials(completeName)}</span>
6161
</div>

src/context/authContext.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { useState, createContext, useEffect, useRef } from "react";
22
import { getUserLogged } from "../services/authService";
33
import { io } from "socket.io-client";
44

5-
65
export const AuthContext = createContext();
76

87
const BASE_URL = import.meta.env.VITE_BASE_URL;
@@ -15,18 +14,21 @@ export const AuthProvider = ({ children }) => {
1514
);
1615
const [onlineUsers, setOnlineUsers] = useState([]);
1716
const [notifications, setNotifications] = useState([]);
17+
const [isCheckingOnboarding, setIsCheckingOnboarding] = useState(false);
1818

19-
2019
// Mantén el socket en una ref para que no cause re-render
2120
const socketRef = useRef(null);
2221
const infoUserLogged = async () => {
2322
try {
23+
setIsCheckingOnboarding(true);
2424
const resp = await getUserLogged(token);
2525
setProfile(resp);
2626
} catch (err) {
2727
setProfile(null);
2828
setToken(null);
2929
localStorage.removeItem("token");
30+
} finally {
31+
setIsCheckingOnboarding(false);
3032
}
3133
};
3234

@@ -78,7 +80,6 @@ export const AuthProvider = ({ children }) => {
7880
socketRef.current.disconnect();
7981
socketRef.current = null;
8082
}
81-
8283
};
8384

8485
return (
@@ -93,6 +94,7 @@ export const AuthProvider = ({ children }) => {
9394
notifications,
9495
setNotifications,
9596
socket: socketRef.current,
97+
isCheckingOnboarding,
9698
}}
9799
>
98100
{children}

src/features/auth/login.jsx

Lines changed: 95 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,108 @@
1-
import logo from '../../assets/Codepply-Logotype-gradient.svg'
2-
import { useContext, useEffect } from 'react'
3-
import { useForm } from 'react-hook-form';
4-
import { Link, useLocation } from 'react-router';
5-
import { AuthContext } from '../../context/authContext';
1+
import logo from "../../assets/Codepply-Logotype-gradient.svg";
2+
import { useContext, useEffect } from "react";
3+
import { useForm } from "react-hook-form";
4+
import { Link, useLocation } from "react-router";
5+
import { AuthContext } from "../../context/authContext";
66
import { useNavigate } from "react-router";
7-
import { loginUser } from '../../services/authService';
7+
import { loginUser } from "../../services/authService";
88
export const Login = () => {
9-
const { register,
10-
watch,
11-
handleSubmit,
12-
formState: { errors } } = useForm();
13-
const navigate = useNavigate();
14-
const location = useLocation();
15-
const { setToken,profile } = useContext(AuthContext);
9+
const {
10+
register,
11+
watch,
12+
handleSubmit,
13+
formState: { errors },
14+
} = useForm();
15+
const navigate = useNavigate();
16+
const location = useLocation();
17+
const { setToken, profile } = useContext(AuthContext);
1618

17-
const login = async () => {
18-
const resp = await loginUser(watch('email'), watch('password'));
19-
localStorage.setItem('token', resp.token);
20-
setToken(resp.token); // Esto dispara el efecto en el contexto
21-
};
19+
const login = async () => {
20+
const resp = await loginUser(watch("email"), watch("password"));
21+
localStorage.setItem("token", resp.token);
22+
setToken(resp.token); // Esto dispara el efecto en el contexto
23+
};
2224

23-
// Cuando el profile se carga, redirigimos
24-
useEffect(() => {
25+
// Cuando el profile se carga, redirigimos
26+
useEffect(() => {
2527
if (profile) {
26-
const from = location.state?.from;
27-
if (from) {
28-
navigate(from, { replace: true });
29-
} else if (profile.role.type === 'developer') {
30-
navigate(`/profile/${profile._id}`, { replace: true });
31-
} else {
32-
navigate(`/recruiter/${profile._id}`, { replace: true });
33-
}
34-
}
35-
}, [profile]);
36-
37-
return (
38-
<div className="flex items-center justify-center px-4 mt-32 mb-56">
39-
<div className="card w-full max-w-md bg-neutral-80 shadow-2xl border-1 border-neutral-60">
28+
const from = location.state?.from;
4029

41-
<div className="card-body">
42-
<h2 className="text-2xl font-bold text-center mb-4 mt-4">Login Account </h2>
43-
44-
<form onSubmit={handleSubmit(login)} className="space-y-3">
45-
<div className="form-control">
46-
<label className="label">
47-
<span className="label-text text-neutral-20">Email</span>
48-
</label>
49-
<input
50-
type="text"
51-
placeholder="Your email"
52-
className="input input-bordered input-md w-full bg-neutral-60 border-neutral-50"
53-
{...register('email', {
54-
required: {
55-
value: true,
56-
message: 'El campo es requerido'
57-
},
58-
minLength: {
59-
value: 4,
60-
message: 'El campo debe tener al menos 4 caracteres'
61-
}
62-
})}
63-
/>
64-
{
65-
errors.name && <p style={{ color: 'red' }}>{errors.name.message}</p>
66-
}
67-
</div>
30+
if (!profile.hasCompletedOnboarding) {
31+
navigate("/onboarding", { replace: true }); // 👈 redirige a onboarding
32+
} else if (from) {
33+
navigate(from, { replace: true });
34+
} else if (profile.role.type === "developer") {
35+
navigate(`/profile/${profile._id}`, { replace: true });
36+
} else {
37+
navigate(`/recruiter/${profile._id}`, { replace: true });
38+
}
39+
}
40+
}, [profile]);
6841

69-
<div className="form-control">
70-
<label className="label">
71-
<span className="label-text text-neutral-20">Password</span>
72-
</label>
73-
<input
74-
{...register('password')}
75-
type="password"
76-
placeholder="Your password"
77-
className="input input-bordered input-md w-full bg-neutral-60 border-neutral-50"
78-
/>
79-
</div>
42+
return (
43+
<div className="flex items-center justify-center px-4 mt-32 mb-56">
44+
<div className="card w-full max-w-md bg-neutral-80 shadow-2xl border-1 border-neutral-60">
45+
<div className="card-body">
46+
<h2 className="text-2xl font-bold text-center mb-4 mt-4">
47+
Login Account{" "}
48+
</h2>
8049

81-
<div className="form-control mt-6">
82-
<button className="btn bg-gradient-to-r from-primary-60 to-secondary-60 w-full text-lg text-neutral-0 font-semibold tracking-wide hover:bg-primary-70">
83-
Log in
84-
</button>
85-
</div>
86-
</form>
50+
<form onSubmit={handleSubmit(login)} className="space-y-3">
51+
<div className="form-control">
52+
<label className="label">
53+
<span className="label-text text-neutral-20">Email</span>
54+
</label>
55+
<input
56+
type="text"
57+
placeholder="Your email"
58+
className="input input-bordered input-md w-full bg-neutral-60 border-neutral-50"
59+
{...register("email", {
60+
required: {
61+
value: true,
62+
message: "El campo es requerido",
63+
},
64+
minLength: {
65+
value: 4,
66+
message: "El campo debe tener al menos 4 caracteres",
67+
},
68+
})}
69+
/>
70+
{errors.name && (
71+
<p style={{ color: "red" }}>{errors.name.message}</p>
72+
)}
73+
</div>
8774

88-
<p className="text-sm text-center mt-2">
89-
Don't have an account yet?{' '}
90-
<Link to={'/register'} className="text-primary-50 hover:underline">Sign Up</Link>
91-
</p>
92-
<img
93-
src={logo}
94-
alt="Codepply Logo"
95-
className="h-6 mx-auto mt-8"
96-
/>
97-
<small className="text-[11px] text-neutral-30 text-center block mt-1">
98-
Codepply Spain ® 2025
99-
</small>
75+
<div className="form-control">
76+
<label className="label">
77+
<span className="label-text text-neutral-20">Password</span>
78+
</label>
79+
<input
80+
{...register("password")}
81+
type="password"
82+
placeholder="Your password"
83+
className="input input-bordered input-md w-full bg-neutral-60 border-neutral-50"
84+
/>
85+
</div>
10086

101-
</div>
87+
<div className="form-control mt-6">
88+
<button className="btn bg-gradient-to-r from-primary-60 to-secondary-60 w-full text-lg text-neutral-0 font-semibold tracking-wide hover:bg-primary-70">
89+
Log in
90+
</button>
10291
</div>
92+
</form>
93+
94+
<p className="text-sm text-center mt-2">
95+
Don't have an account yet?{" "}
96+
<Link to={"/register"} className="text-primary-50 hover:underline">
97+
Sign Up
98+
</Link>
99+
</p>
100+
<img src={logo} alt="Codepply Logo" className="h-6 mx-auto mt-8" />
101+
<small className="text-[11px] text-neutral-30 text-center block mt-1">
102+
Codepply Spain ® 2025
103+
</small>
103104
</div>
104-
);
105+
</div>
106+
</div>
107+
);
105108
};

src/features/developer/components/DevsCard.jsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Link } from "react-router";
2+
import { AvatarImage } from "../../../components/AvatarImage";
3+
import { NameUsers } from "../../../components/NameUsers";
24

35
const DevsCard = ({
4-
name,
5-
surname,
6-
avatar,
6+
developer,
77
profession,
88
experienceYears,
99
location,
@@ -19,18 +19,13 @@ const DevsCard = ({
1919
<div>
2020
{/* Avatar */}
2121
<div className="flex justify-center mt-1 mb-2">
22-
<img
23-
src={avatar}
24-
alt="Avatar"
25-
className="w-16 h-16 rounded-full border-2 border-neutral-60"
26-
/>
22+
<AvatarImage user={developer} width={16} />
2723
</div>
2824

2925
{/* Name */}
3026
<div className="text-center">
31-
<span className="text-neutral-0 font-semibold text-lg leading-tight group-hover:text-primary-40">
32-
{name} {surname}
33-
</span>
27+
<NameUsers classProps={"font-semibold text-lg leading-tight group-hover:text-primary-40 "} user={developer} />
28+
3429
</div>
3530

3631
{/* Profession + Experience */}

src/features/developer/pages/DevsPage.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ export const DevsPage = () => {
375375
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8">
376376
{currentDevs.map((developer) => (
377377
<DevsCard
378+
developer={developer}
378379
key={developer._id}
379380
developerId={developer._id}
380381
name={developer.name}

src/features/recruiters/components/ListDashBoard.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { TabsDashboard } from './TabsDashboard';
44
import { ListDashBoardCard } from './ListDashBoardCard';
55

66

7-
export const ListDashBoard = ({ classProps, offerId, lists, setLists, getCandidates,skillsOffer }) => {
7+
export const ListDashBoard = ({ classProps, offerId, lists, setLists, getCandidates,skillsOffer, openChat, handlerDownloadCV, handlerDownloadCoverLetter }) => {
88
const [activeTab, setActiveTab] = useState(Object.keys(lists)[0]);
99

1010
const changeStatusCandidate = async (status, idCandidato) => {
@@ -43,7 +43,9 @@ export const ListDashBoard = ({ classProps, offerId, lists, setLists, getCandida
4343

4444

4545
{/* Contenido de la pestaña activa */}
46-
<ListDashBoardCard lists={lists} activeTab={activeTab} setActiveTab={setActiveTab} skillsOffer={skillsOffer} colors={colors} fadedColors={fadedColors} textColors={textColors} changeStatusCandidate={changeStatusCandidate} />
46+
<ListDashBoardCard lists={lists} activeTab={activeTab}
47+
handlerDownloadCV={handlerDownloadCV} handlerDownloadCoverLetter={handlerDownloadCoverLetter} openChat={openChat}
48+
setActiveTab={setActiveTab} skillsOffer={skillsOffer} colors={colors} fadedColors={fadedColors} textColors={textColors} changeStatusCandidate={changeStatusCandidate} offerId={offerId} />
4749

4850
</div>
4951

src/features/recruiters/components/ListDashBoardCard.jsx

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,15 @@ import { PiChat, PiEnvelope, PiFileArrowDown, PiMapPinArea, PiReadCvLogo } from
22
import { AvatarImage } from "../../../components/AvatarImage"
33
import { NameUsers } from "../../../components/NameUsers"
44
import { CandidateSkills } from "./candidateSkills"
5-
import { useContext } from "react"
6-
import { ChatContext } from "../../../layout/chat/context/ChatContext"
5+
76
import { GoChevronDown } from "react-icons/go"
87
import { capitalize } from "../../../utils/utils"
98

109

11-
export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedColors, textColors, changeStatusCandidate }) => {
1210

11+
export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedColors, textColors, changeStatusCandidate, offerId, openChat, handlerDownloadCV, handlerDownloadCoverLetter }) => {
1312

1413

15-
const handleDownloadCV = async (resumeUrl, fileName = 'CV.pdf') => {
16-
try {
17-
const response = await fetch(resumeUrl);
18-
const blob = await response.blob();
19-
20-
// Crear un enlace temporal para la descarga
21-
const url = window.URL.createObjectURL(blob);
22-
const link = document.createElement('a');
23-
link.href = url;
24-
link.download = fileName;
25-
document.body.appendChild(link);
26-
link.click();
27-
28-
// Limpiar
29-
document.body.removeChild(link);
30-
window.URL.revokeObjectURL(url);
31-
} catch (error) {
32-
console.error('Error al descargar el CV:', error);
33-
// Fallback: abrir en nueva pestaña
34-
window.open(resumeUrl, '_blank');
35-
}
36-
};
37-
38-
const {openChat} = useContext(ChatContext);
3914

4015

4116
return (
@@ -47,6 +22,7 @@ export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedC
4722
const surname = capitalize(candidato?.user?.surname || '');
4823
const completeName = `${name} ${surname}`.trim() || 'Unknown Profile';
4924
const isResume = candidato?.user?.role?.developer?.resume;
25+
const isCoverLetter = (candidato?.user?.role?.developer?.coverLetter?.length ?? 0) >= 5;
5026
return (
5127
<div key={candidato._id} className="bg-neutral-80 p-4 gap-2 rounded-lg shadow-sm border border-neutral-60 flex flex-col">
5228
<div
@@ -119,7 +95,7 @@ export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedC
11995

12096
<button
12197
onClick={() =>
122-
handleDownloadCV(
98+
handlerDownloadCV(
12399
candidato.user.role.developer.resume,
124100
`${completeName}_CV.pdf`
125101
)
@@ -129,8 +105,8 @@ export const ListDashBoardCard = ({lists, activeTab, skillsOffer, colors, fadedC
129105
<PiReadCvLogo size={20} />
130106
</button>
131107
<button
132-
133-
className='btn btn-md bg-neutral-90 hover:bg-neutral-60'
108+
onClick={() => handlerDownloadCoverLetter(offerId, candidato._id)}
109+
className={`btn btn-md bg-neutral-90 hover:bg-neutral-60 ${!isCoverLetter && 'btn-disabled'} `}
134110
>
135111
<PiFileArrowDown size={20} />
136112
</button>

0 commit comments

Comments
 (0)