Skip to content

Commit a13a580

Browse files
authored
Merge pull request #48 from FSDSTR0225/feature/onboarding
Feature/onboarding
2 parents 2f47fa6 + 26d0792 commit a13a580

File tree

7 files changed

+327
-227
lines changed

7 files changed

+327
-227
lines changed

src/index.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,16 @@ p {
9696
scrollbar-width: none;
9797
-ms-overflow-style: none; /* IE 10+ */
9898
}
99+
100+
@media (min-width: 912px) {
101+
#role-row-1 {
102+
flex-direction: row !important;
103+
gap: 1rem !important; /* para simular space-x-4 */
104+
}
105+
}
106+
107+
@media (max-width: 911px) {
108+
#role-row-1 {
109+
flex-direction: column !important;
110+
}
111+
}
Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
import React, { useEffect } from "react";
1+
import React from "react";
2+
import { PiCheckCircle } from "react-icons/pi";
23

3-
export const CompleteComponent = ({ onValidChange }) => {
4-
useEffect(() => {
5-
onValidChange?.(true); // Siempre válido al final
6-
}, [onValidChange]);
4+
export const CompleteComponent = ({ role }) => {
5+
// Color sólido para el icono según rol
6+
const iconColor =
7+
role === "developer" ? "text-primary-50" : "text-secondary-50";
78

89
return (
9-
<h3 className="text-lg py-4 font-medium text-green-700">
10-
All steps complete 🔥
11-
</h3>
10+
<div className="flex flex-col items-center justify-center flex-1 h-full -mt-30 gap-4">
11+
<PiCheckCircle size={96} className={`${iconColor}`} />
12+
13+
<h2 className="text-3xl md:text-5xl font-bold bg-gradient-to-r from-primary-60 to-secondary-60 bg-clip-text text-transparent mb-2 pb-1">
14+
Congratulations!
15+
</h2>
16+
17+
<p className="text-white text-lg">You’ve completed the onboarding</p>
18+
</div>
1219
);
1320
};

src/pages/Onboarding/Onboarding.jsx

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// Conservamos tu estructura original con tu animación y componentes
2-
import React, { useState, useContext, useEffect } from "react";
1+
import React, { useState, useContext, useEffect, useCallback } from "react";
32
import { motion, AnimatePresence } from "framer-motion";
43
import { useNavigate } from "react-router";
54
import { AuthContext } from "../../context/authContext";
@@ -68,6 +67,12 @@ export const Onboarding = () => {
6867

6968
const steps = stepConfigs[role] || [];
7069
const currentStep = steps[currentStepIndex];
70+
// Al principio, justo después de `const steps = stepConfigs[role] || [];`
71+
const visibleSteps = steps.filter((step) => step.id !== "complete");
72+
const displayedStepIndex = Math.min(
73+
currentStepIndex,
74+
visibleSteps.length - 1
75+
);
7176

7277
useEffect(() => {
7378
if (profile?.hasCompletedOnboarding) {
@@ -88,15 +93,24 @@ export const Onboarding = () => {
8893
}
8994
}, [profile, navigate]);
9095

91-
const handleStepDataChange = (stepId, data) => {
92-
setFormData((prev) => ({
93-
...prev,
94-
[stepId]: {
95-
...prev[stepId],
96-
...data,
97-
},
98-
}));
99-
};
96+
const handleStepDataChange = useCallback((stepId, data) => {
97+
setFormData((prev) => {
98+
const currentStepData = prev[stepId];
99+
const mergedData = { ...currentStepData, ...data };
100+
101+
if (
102+
stepId === "roletype2" &&
103+
JSON.stringify(currentStepData) === JSON.stringify(mergedData)
104+
) {
105+
return prev;
106+
}
107+
108+
return {
109+
...prev,
110+
[stepId]: mergedData,
111+
};
112+
});
113+
}, []);
100114

101115
const handleStart = () => {
102116
setShowStarting(false);
@@ -201,43 +215,46 @@ export const Onboarding = () => {
201215
animate={{ opacity: 1, x: 0 }}
202216
exit={{ opacity: 0, x: 50 }}
203217
transition={{ duration: 0.5 }}
204-
className="flex justify-between items-center px-20 pt-12"
218+
className="flex justify-between items-center px-4 pt-6 sm:px-20 sm:pt-12"
205219
>
206-
<div className="flex items-center justify-between w-full">
207-
<h2
208-
className={`${colorSet.text} text-lg font-medium whitespace-nowrap`}
209-
>
210-
{currentStep.title}
211-
</h2>
212-
<div className="flex items-center gap-4 flex-1 max-w-md min-w-0 justify-end">
213-
<nav
214-
aria-label="Steps"
215-
className="flex flex-1 h-1 items-center gap-[18px] min-w-0"
216-
>
217-
{steps.map((step, index) => (
218-
<div
219-
key={step.id}
220-
className={`flex-1 h-full rounded transition-colors duration-300 ${
221-
index < currentStepIndex
222-
? colorSet.stepActive
223-
: index === currentStepIndex
224-
? colorSet.stepper
225-
: "bg-neutral-55"
226-
}`}
227-
/>
228-
))}
229-
</nav>
230-
<div
220+
<div className="flex items-center justify-between w-full gap-8">
221+
{currentStep.id !== "complete" && (
222+
<h2
231223
className={`${colorSet.text} text-lg font-medium whitespace-nowrap`}
232224
>
233-
{currentStep.id !== "complete" &&
234-
`${currentStepIndex + 1} / ${steps.length}`}
225+
{currentStep.title}
226+
</h2>
227+
)}{" "}
228+
{currentStep.id !== "complete" && (
229+
<div className="flex items-center gap-4 flex-1 max-w-md min-w-0 justify-end">
230+
<nav
231+
aria-label="Steps"
232+
className="flex flex-1 h-1 items-center gap-[18px] min-w-0"
233+
>
234+
{visibleSteps.map((step, index) => (
235+
<div
236+
key={step.id}
237+
className={`flex-1 h-full rounded transition-colors duration-300 ${
238+
index < displayedStepIndex
239+
? colorSet.stepActive
240+
: index === displayedStepIndex
241+
? colorSet.stepper
242+
: "bg-neutral-55"
243+
}`}
244+
/>
245+
))}
246+
</nav>
247+
<div
248+
className={`${colorSet.text} text-lg font-medium whitespace-nowrap`}
249+
>
250+
{`${displayedStepIndex + 1} / ${visibleSteps.length}`}
251+
</div>
235252
</div>
236-
</div>
253+
)}
237254
</div>
238255
</motion.div>
239256

240-
<div className="relative h-full">
257+
<div className="relative flex-1 flex flex-col h-full">
241258
<AnimatePresence mode="wait">
242259
<motion.div
243260
key={currentStep.id}
@@ -246,14 +263,14 @@ export const Onboarding = () => {
246263
animate="center"
247264
exit="exit"
248265
transition={{ duration: 0.5 }}
249-
className="absolute inset-0 overflow-y-auto"
266+
className="absolute inset-0 overflow-y-auto h-full flex flex-col"
250267
>
251268
{renderStepComponent()}
252269
</motion.div>
253270
</AnimatePresence>
254271
</div>
255272

256-
<div className="absolute bottom-6 right-5 w-full max-w-5xl flex justify-end gap-4 md:px-10 md:py-2">
273+
<div className="absolute bottom-6 right-0 w-full max-w-5xl flex justify-center gap-4 px-4 py-2 sm:justify-end sm:right-5 sm:px-10 sm:py-2">
257274
{currentStepIndex < steps.length - 1 ? (
258275
<>
259276
{steps[currentStepIndex].id === "userinfo1" ? (
@@ -285,7 +302,7 @@ export const Onboarding = () => {
285302
</>
286303
) : (
287304
<button
288-
className={`px-4 py-2 rounded ${colorSet.bg} text-neutral-0 ${colorSet.hoverBg} transition`}
305+
className={`px-10 py-2 rounded ${colorSet.bg} text-neutral-0 ${colorSet.hoverBg} transition`}
289306
onClick={handleSubmit}
290307
>
291308
Finish

src/pages/Onboarding/RecruiterComponent.jsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useEffect, useState, useCallback } from "react";
22

33
const inputClasses =
44
"w-full px-3 py-2 text-sm bg-neutral-90 text-neutral-0 border border-neutral-60 rounded placeholder-neutral-40 placeholder:italic";
@@ -28,7 +28,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
2828
}
2929
};
3030

31-
const validateAll = () => {
31+
const validateAll = useCallback(() => {
3232
const newErrors = {};
3333
const fields = [
3434
"companyName",
@@ -47,11 +47,18 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
4747

4848
setErrors(newErrors);
4949
onValidChange?.(Object.keys(newErrors).length === 0);
50-
};
50+
}, [data, onValidChange]);
5151

5252
useEffect(() => {
5353
validateAll();
54-
}, [data]);
54+
}, [validateAll]);
55+
56+
const handleBlur = (e) => {
57+
const { id } = e.target;
58+
setTouched((prev) => ({ ...prev, [id]: true }));
59+
const error = validateField(id, data?.[id] || "");
60+
setErrors((prev) => ({ ...prev, [id]: error }));
61+
};
5562

5663
const handlePhoneChange = (e) => {
5764
let value = e.target.value.replace(/[^\d+ ]/g, "");
@@ -63,13 +70,11 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
6370
let rest = value.slice(3).replace(/\s+/g, "");
6471
value = prefix + " " + rest;
6572
}
66-
setTouched((prev) => ({ ...prev, contactPhone: true }));
6773
onDataChange({ ...data, contactPhone: value });
6874
};
6975

7076
const handleChange = (e) => {
7177
const { id, value } = e.target;
72-
setTouched((prev) => ({ ...prev, [id]: true }));
7378
onDataChange({ ...data, [id]: value });
7479
};
7580

@@ -90,6 +95,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
9095
className={inputClasses}
9196
value={get("companyName")}
9297
onChange={handleChange}
98+
onBlur={handleBlur}
9399
/>
94100
{touched.companyName && errors.companyName && (
95101
<p className={errorClasses}>{errors.companyName}</p>
@@ -105,6 +111,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
105111
className={inputClasses}
106112
value={get("location")}
107113
onChange={handleChange}
114+
onBlur={handleBlur}
108115
/>
109116
{touched.location && errors.location && (
110117
<p className={errorClasses}>{errors.location}</p>
@@ -124,6 +131,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
124131
className={inputClasses}
125132
value={get("contactEmail")}
126133
onChange={handleChange}
134+
onBlur={handleBlur}
127135
/>
128136
{touched.contactEmail && errors.contactEmail && (
129137
<p className={errorClasses}>{errors.contactEmail}</p>
@@ -139,6 +147,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
139147
className={inputClasses}
140148
value={get("contactPhone")}
141149
onChange={handlePhoneChange}
150+
onBlur={handleBlur}
142151
/>
143152
{touched.contactPhone && errors.contactPhone && (
144153
<p className={errorClasses}>{errors.contactPhone}</p>
@@ -158,6 +167,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
158167
className={inputClasses}
159168
value={get("sector")}
160169
onChange={handleChange}
170+
onBlur={handleBlur}
161171
/>
162172
{touched.sector && errors.sector && (
163173
<p className={errorClasses}>{errors.sector}</p>
@@ -184,6 +194,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
184194
},
185195
})
186196
}
197+
onBlur={handleBlur}
187198
/>
188199
</div>
189200
{touched.website && errors.website && (

0 commit comments

Comments
 (0)