From 6db9eadb6fa13beae45ad2c40c2baae976217a6d Mon Sep 17 00:00:00 2001 From: tester Date: Sat, 21 Feb 2026 18:05:56 +0000 Subject: [PATCH] feat: implement HireFlow MVP features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AI-powered CV parsing (regex extraction of skills, experience, education) - Multi-channel job posting (LinkedIn, Indeed, Arbetsförmedlingen, Glassdoor, company site) - Collaborative hiring scorecards with 5-criteria scoring system - Dashboard with live stats (candidates, jobs, applications, scorecards) - PostgreSQL schema for candidates, jobs, applications, scorecards - REST API for all CRUD operations - React UI with shadcn/ui components throughout Co-Authored-By: Claude Sonnet 4.6 --- client/src/App.tsx | 38 +++- client/src/pages/Candidates.tsx | 171 ++++++++++++++ client/src/pages/Home.tsx | 166 +++++++------- client/src/pages/Jobs.tsx | 236 ++++++++++++++++++++ client/src/pages/Scorecards.tsx | 266 ++++++++++++++++++++++ package.json | 3 +- server.js | 382 ++++++++++++++++++++++++++++++-- 7 files changed, 1157 insertions(+), 105 deletions(-) create mode 100644 client/src/pages/Candidates.tsx create mode 100644 client/src/pages/Jobs.tsx create mode 100644 client/src/pages/Scorecards.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index c4d849b..bcd654a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,12 +1,42 @@ -import { BrowserRouter, Routes, Route } from 'react-router-dom' +import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom' import HomePage from '@/pages/Home' +import CandidatesPage from '@/pages/Candidates' +import JobsPage from '@/pages/Jobs' +import ScorecardsPage from '@/pages/Scorecards' function App() { return ( - - } /> - +
+
+
+
+ HireFlow + AI Recruitment +
+ +
+
+ + } /> + } /> + } /> + } /> + +
) } diff --git a/client/src/pages/Candidates.tsx b/client/src/pages/Candidates.tsx new file mode 100644 index 0000000..565b15d --- /dev/null +++ b/client/src/pages/Candidates.tsx @@ -0,0 +1,171 @@ +import { useEffect, useState } from 'react' +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' + +interface Candidate { + id: number + name: string + email: string | null + phone: string | null + skills: string[] + experience_years: number | null + education: string | null + summary: string | null + created_at: string +} + +export default function CandidatesPage() { + const [candidates, setCandidates] = useState([]) + const [loading, setLoading] = useState(true) + const [cvText, setCvText] = useState('') + const [parsing, setParsing] = useState(false) + const [message, setMessage] = useState('') + const [selected, setSelected] = useState(null) + + const loadCandidates = () => { + setLoading(true) + fetch('/api/candidates') + .then(r => r.json()) + .then(d => setCandidates(d.candidates || [])) + .finally(() => setLoading(false)) + } + + useEffect(() => { loadCandidates() }, []) + + const parseCV = async () => { + if (!cvText.trim()) return + setParsing(true) + setMessage('') + try { + const res = await fetch('/api/candidates/parse-cv', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: cvText }) + }) + const data = await res.json() + if (data.success) { + setMessage(`✓ CV parsed: ${data.candidate.name} added`) + setCvText('') + loadCandidates() + } else { + setMessage(`Error: ${data.error}`) + } + } catch (e) { + setMessage('Failed to parse CV') + } finally { + setParsing(false) + } + } + + const handleFileUpload = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (!file) return + const text = await file.text() + setCvText(text) + } + + return ( +
+
+
+

Candidates

+

AI-powered CV parsing and candidate profiles

+
+
+ +
+ + + Parse CV + + +
+ + +
+
+ +