Spaces:
Paused
Paused
Download frontend/src/components/pages/WorkflowPage.tsx from SkillSync-II/skillsync-cli: direct link, hf CLI and curl.
- Browser
- Download file 9.58 kB
-
https://huggingface.co/spaces/SkillSync-II/skillsync-cli/resolve/main/frontend/src/components/pages/WorkflowPage.tsx
- Command line
-
hf download hf://spaces/SkillSync-II/skillsync-cli/frontend/src/components/pages/WorkflowPage.tsx
-
curl -L -o WorkflowPage.tsx https://huggingface.co/spaces/SkillSync-II/skillsync-cli/resolve/main/frontend/src/components/pages/WorkflowPage.tsx
9.58 kB
| import React from "react"; | |
| import { motion } from "framer-motion"; | |
| import { Upload, Search, Check, ArrowRight } from "lucide-react"; | |
| import { Card, Button } from "@/components/common"; | |
| import Header from "@/components/sections/Header"; | |
| import CVUpload from "@/components/sections/CVUpload"; | |
| import { AutoApplyProfile } from "@/components/sections/AutoApplyProfile"; | |
| import JobSearch from "@/components/sections/JobSearch"; | |
| import { useJobStore } from "@/store/appStore"; | |
| type StepValue = "upload" | "search" | "review"; | |
| interface WorkflowPageProps { | |
| onJobsFound?: () => void; | |
| onSettingsClick?: () => void; | |
| onHomeClick?: () => void; | |
| } | |
| const WorkflowPage: React.FC<WorkflowPageProps> = ({ | |
| onJobsFound, | |
| onSettingsClick, | |
| onHomeClick, | |
| }) => { | |
| const [currentStep, setCurrentStep] = React.useState<StepValue>("upload"); | |
| const { cvTemplate, jobs } = useJobStore(); | |
| const steps = [ | |
| { | |
| value: "upload" as StepValue, | |
| label: "Upload CV", | |
| icon: Upload, | |
| completed: !!cvTemplate, | |
| }, | |
| { | |
| value: "search" as StepValue, | |
| label: "Search Jobs", | |
| icon: Search, | |
| completed: jobs.length > 0, | |
| }, | |
| { | |
| value: "review" as StepValue, | |
| label: "Review & Generate", | |
| icon: Check, | |
| completed: false, | |
| }, | |
| ]; | |
| return ( | |
| <div className="min-h-screen bg-gradient-saas"> | |
| <Header onSettingsClick={onSettingsClick} onHomeClick={onHomeClick} /> | |
| <div className="px-4 sm:px-6 lg:px-8 py-12 max-w-6xl mx-auto"> | |
| {/* Progress Steps */} | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| className="mb-12" | |
| > | |
| <div className="flex items-center justify-between mb-8"> | |
| {steps.map((step, index) => ( | |
| <React.Fragment key={step.value}> | |
| <motion.button | |
| whileHover={{ scale: 1.05 }} | |
| onClick={() => setCurrentStep(step.value)} | |
| className={`flex flex-col items-center gap-2 cursor-pointer transition-all ${ | |
| currentStep === step.value | |
| ? "opacity-100" | |
| : "opacity-60 hover:opacity-80" | |
| }`} | |
| > | |
| <div | |
| className={`w-12 h-12 rounded-full flex items-center justify-center transition-all ${ | |
| step.completed || currentStep === step.value | |
| ? "bg-gradient-emerald text-white" | |
| : "bg-slate-700 text-slate-300" | |
| }`} | |
| > | |
| {step.completed && currentStep !== step.value ? ( | |
| <Check className="w-6 h-6" /> | |
| ) : ( | |
| <step.icon className="w-6 h-6" /> | |
| )} | |
| </div> | |
| <span className="text-sm font-medium text-slate-300"> | |
| {step.label} | |
| </span> | |
| </motion.button> | |
| {index < steps.length - 1 && ( | |
| <div className="flex-1 h-1 mx-4 bg-slate-700 rounded-full overflow-hidden"> | |
| <motion.div | |
| layoutId="progress" | |
| className="h-full bg-gradient-emerald" | |
| initial={{ width: 0 }} | |
| animate={{ | |
| width: | |
| currentStep === steps[index + 1].value | |
| ? "100%" | |
| : "0%", | |
| }} | |
| transition={{ duration: 0.5 }} | |
| /> | |
| </div> | |
| )} | |
| </React.Fragment> | |
| ))} | |
| </div> | |
| </motion.div> | |
| {/* Content */} | |
| <motion.div | |
| key={currentStep} | |
| initial={{ opacity: 0, y: 20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -20 }} | |
| transition={{ duration: 0.3 }} | |
| > | |
| {currentStep === "upload" && ( | |
| // <div> | |
| // <Card className="mb-8"> | |
| // <h2 className="text-2xl font-bold text-white mb-2"> | |
| // Upload Your CV | |
| // </h2> | |
| // <p className="text-slate-400 mb-6"> | |
| // Start by uploading your base CV template. We'll use this to | |
| // create tailored versions for each job. | |
| // </p> | |
| // <CVUpload | |
| // onSuccess={() => { | |
| // // Auto-proceed to next step | |
| // setTimeout(() => { | |
| // setCurrentStep('search'); | |
| // }, 500); | |
| // }} | |
| // onError={() => { | |
| // // Stay on current step on error | |
| // }} | |
| // /> | |
| // </Card> | |
| // {cvTemplate && ( | |
| // <motion.div | |
| // initial={{ opacity: 0, y: 10 }} | |
| // animate={{ opacity: 1, y: 0 }} | |
| // className="flex justify-end" | |
| // > | |
| // <Button | |
| // onClick={() => setCurrentStep('search')} | |
| // icon={<ArrowRight className="w-4 h-4" />} | |
| // iconPosition="right" | |
| // > | |
| // Proceed to Job Search | |
| // </Button> | |
| // </motion.div> | |
| // )} | |
| // </div> | |
| <div className="space-y-6"> | |
| {" "} | |
| {/* Added spacing container */} | |
| <Card> | |
| <h2 className="text-2xl font-bold text-white mb-2"> | |
| Upload Your CV | |
| </h2> | |
| <p className="text-slate-400 mb-6"> | |
| Start by uploading your base CV template. We'll use this to | |
| create tailored versions for each job. | |
| </p> | |
| <CVUpload | |
| onSuccess={() => { | |
| // REMOVED the setTimeout auto-proceed! | |
| // We want the user to stay here so they can optionally setup Auto-Apply. | |
| console.log("CV Uploaded Successfully"); | |
| }} | |
| onError={() => { | |
| // Stay on current step on error | |
| }} | |
| /> | |
| </Card> | |
| {/* --- NEW OPTIONAL AUTO-APPLY SECTION --- */} | |
| <AutoApplyProfile /> | |
| {/* PROCEED BUTTON */} | |
| {cvTemplate && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="flex justify-end pt-4" | |
| > | |
| <Button | |
| onClick={() => setCurrentStep("search")} | |
| icon={<ArrowRight className="w-4 h-4" />} | |
| iconPosition="right" | |
| > | |
| Proceed to Job Search | |
| </Button> | |
| </motion.div> | |
| )} | |
| </div> | |
| )} | |
| {currentStep === "search" && ( | |
| <div> | |
| <Card className="mb-8"> | |
| <h2 className="text-2xl font-bold text-white mb-2"> | |
| Search for Jobs | |
| </h2> | |
| <p className="text-slate-400 mb-6"> | |
| Find job opportunities that match your skills and interests. | |
| </p> | |
| <JobSearch | |
| onJobsFound={() => { | |
| onJobsFound?.(); | |
| // Auto-proceed to next step after small delay | |
| setTimeout(() => { | |
| setCurrentStep("review"); | |
| }, 800); | |
| }} | |
| /> | |
| </Card> | |
| </div> | |
| )} | |
| {currentStep === "review" && ( | |
| <div> | |
| <Card> | |
| <h2 className="text-2xl font-bold text-white mb-2"> | |
| Ready to Generate CVs | |
| </h2> | |
| <p className="text-slate-400 mb-6"> | |
| You have {jobs.length} jobs ready. Select the ones you want to | |
| generate tailored CVs for and download them all at once. | |
| </p> | |
| {jobs.length > 0 ? ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| > | |
| <Button | |
| fullWidth | |
| size="lg" | |
| icon={<ArrowRight className="w-5 h-5" />} | |
| iconPosition="right" | |
| onClick={() => { | |
| console.log( | |
| "View Jobs button clicked! Jobs count:", | |
| jobs.length | |
| ); | |
| onJobsFound?.(); | |
| }} | |
| > | |
| View Jobs & Generate CVs | |
| </Button> | |
| </motion.div> | |
| ) : ( | |
| <div className="p-4 bg-slate-800/30 border border-slate-700 rounded-lg text-center"> | |
| <p className="text-slate-400 text-sm"> | |
| No jobs found. Please try searching again. | |
| </p> | |
| </div> | |
| )} | |
| </Card> | |
| </div> | |
| )} | |
| </motion.div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default WorkflowPage; | |