Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,16 @@ p {
scrollbar-width: none;
-ms-overflow-style: none; /* IE 10+ */
}

@media (min-width: 912px) {
#role-row-1 {
flex-direction: row !important;
gap: 1rem !important; /* para simular space-x-4 */
}
}

@media (max-width: 911px) {
#role-row-1 {
flex-direction: column !important;
}
}
23 changes: 15 additions & 8 deletions src/pages/Onboarding/CompleteComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import React, { useEffect } from "react";
import React from "react";
import { PiCheckCircle } from "react-icons/pi";

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

return (
<h3 className="text-lg py-4 font-medium text-green-700">
All steps complete 🔥
</h3>
<div className="flex flex-col items-center justify-center flex-1 h-full -mt-30 gap-4">
<PiCheckCircle size={96} className={`${iconColor}`} />

<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">
Congratulations!
</h2>

<p className="text-white text-lg">You’ve completed the onboarding</p>
</div>
);
};
105 changes: 61 additions & 44 deletions src/pages/Onboarding/Onboarding.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Conservamos tu estructura original con tu animación y componentes
import React, { useState, useContext, useEffect } from "react";
import React, { useState, useContext, useEffect, useCallback } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useNavigate } from "react-router";
import { AuthContext } from "../../context/authContext";
Expand Down Expand Up @@ -68,6 +67,12 @@ export const Onboarding = () => {

const steps = stepConfigs[role] || [];
const currentStep = steps[currentStepIndex];
// Al principio, justo después de `const steps = stepConfigs[role] || [];`
const visibleSteps = steps.filter((step) => step.id !== "complete");
const displayedStepIndex = Math.min(
currentStepIndex,
visibleSteps.length - 1
);

useEffect(() => {
if (profile?.hasCompletedOnboarding) {
Expand All @@ -88,15 +93,24 @@ export const Onboarding = () => {
}
}, [profile, navigate]);

const handleStepDataChange = (stepId, data) => {
setFormData((prev) => ({
...prev,
[stepId]: {
...prev[stepId],
...data,
},
}));
};
const handleStepDataChange = useCallback((stepId, data) => {
setFormData((prev) => {
const currentStepData = prev[stepId];
const mergedData = { ...currentStepData, ...data };

if (
stepId === "roletype2" &&
JSON.stringify(currentStepData) === JSON.stringify(mergedData)
) {
return prev;
}

return {
...prev,
[stepId]: mergedData,
};
});
}, []);

const handleStart = () => {
setShowStarting(false);
Expand Down Expand Up @@ -201,43 +215,46 @@ export const Onboarding = () => {
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
transition={{ duration: 0.5 }}
className="flex justify-between items-center px-20 pt-12"
className="flex justify-between items-center px-4 pt-6 sm:px-20 sm:pt-12"
>
<div className="flex items-center justify-between w-full">
<h2
className={`${colorSet.text} text-lg font-medium whitespace-nowrap`}
>
{currentStep.title}
</h2>
<div className="flex items-center gap-4 flex-1 max-w-md min-w-0 justify-end">
<nav
aria-label="Steps"
className="flex flex-1 h-1 items-center gap-[18px] min-w-0"
>
{steps.map((step, index) => (
<div
key={step.id}
className={`flex-1 h-full rounded transition-colors duration-300 ${
index < currentStepIndex
? colorSet.stepActive
: index === currentStepIndex
? colorSet.stepper
: "bg-neutral-55"
}`}
/>
))}
</nav>
<div
<div className="flex items-center justify-between w-full gap-8">
{currentStep.id !== "complete" && (
<h2
className={`${colorSet.text} text-lg font-medium whitespace-nowrap`}
>
{currentStep.id !== "complete" &&
`${currentStepIndex + 1} / ${steps.length}`}
{currentStep.title}
</h2>
)}{" "}
{currentStep.id !== "complete" && (
<div className="flex items-center gap-4 flex-1 max-w-md min-w-0 justify-end">
<nav
aria-label="Steps"
className="flex flex-1 h-1 items-center gap-[18px] min-w-0"
>
{visibleSteps.map((step, index) => (
<div
key={step.id}
className={`flex-1 h-full rounded transition-colors duration-300 ${
index < displayedStepIndex
? colorSet.stepActive
: index === displayedStepIndex
? colorSet.stepper
: "bg-neutral-55"
}`}
/>
))}
</nav>
<div
className={`${colorSet.text} text-lg font-medium whitespace-nowrap`}
>
{`${displayedStepIndex + 1} / ${visibleSteps.length}`}
</div>
</div>
</div>
)}
</div>
</motion.div>

<div className="relative h-full">
<div className="relative flex-1 flex flex-col h-full">
<AnimatePresence mode="wait">
<motion.div
key={currentStep.id}
Expand All @@ -246,14 +263,14 @@ export const Onboarding = () => {
animate="center"
exit="exit"
transition={{ duration: 0.5 }}
className="absolute inset-0 overflow-y-auto"
className="absolute inset-0 overflow-y-auto h-full flex flex-col"
>
{renderStepComponent()}
</motion.div>
</AnimatePresence>
</div>

<div className="absolute bottom-6 right-5 w-full max-w-5xl flex justify-end gap-4 md:px-10 md:py-2">
<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">
{currentStepIndex < steps.length - 1 ? (
<>
{steps[currentStepIndex].id === "userinfo1" ? (
Expand Down Expand Up @@ -285,7 +302,7 @@ export const Onboarding = () => {
</>
) : (
<button
className={`px-4 py-2 rounded ${colorSet.bg} text-neutral-0 ${colorSet.hoverBg} transition`}
className={`px-10 py-2 rounded ${colorSet.bg} text-neutral-0 ${colorSet.hoverBg} transition`}
onClick={handleSubmit}
>
Finish
Expand Down
23 changes: 17 additions & 6 deletions src/pages/Onboarding/RecruiterComponent.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";

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

const validateAll = () => {
const validateAll = useCallback(() => {
const newErrors = {};
const fields = [
"companyName",
Expand All @@ -47,11 +47,18 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {

setErrors(newErrors);
onValidChange?.(Object.keys(newErrors).length === 0);
};
}, [data, onValidChange]);

useEffect(() => {
validateAll();
}, [data]);
}, [validateAll]);

const handleBlur = (e) => {
const { id } = e.target;
setTouched((prev) => ({ ...prev, [id]: true }));
const error = validateField(id, data?.[id] || "");
setErrors((prev) => ({ ...prev, [id]: error }));
};

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

const handleChange = (e) => {
const { id, value } = e.target;
setTouched((prev) => ({ ...prev, [id]: true }));
onDataChange({ ...data, [id]: value });
};

Expand All @@ -90,6 +95,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
className={inputClasses}
value={get("companyName")}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched.companyName && errors.companyName && (
<p className={errorClasses}>{errors.companyName}</p>
Expand All @@ -105,6 +111,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
className={inputClasses}
value={get("location")}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched.location && errors.location && (
<p className={errorClasses}>{errors.location}</p>
Expand All @@ -124,6 +131,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
className={inputClasses}
value={get("contactEmail")}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched.contactEmail && errors.contactEmail && (
<p className={errorClasses}>{errors.contactEmail}</p>
Expand All @@ -139,6 +147,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
className={inputClasses}
value={get("contactPhone")}
onChange={handlePhoneChange}
onBlur={handleBlur}
/>
{touched.contactPhone && errors.contactPhone && (
<p className={errorClasses}>{errors.contactPhone}</p>
Expand All @@ -158,6 +167,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
className={inputClasses}
value={get("sector")}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched.sector && errors.sector && (
<p className={errorClasses}>{errors.sector}</p>
Expand All @@ -184,6 +194,7 @@ export const RecruiterComponent = ({ data, onDataChange, onValidChange }) => {
},
})
}
onBlur={handleBlur}
/>
</div>
{touched.website && errors.website && (
Expand Down
Loading