diff --git a/src/pages/Analytics.jsx b/src/pages/Analytics.tsx similarity index 100% rename from src/pages/Analytics.jsx rename to src/pages/Analytics.tsx diff --git a/src/pages/Apply.jsx b/src/pages/Apply.tsx similarity index 100% rename from src/pages/Apply.jsx rename to src/pages/Apply.tsx diff --git a/src/pages/Candidates.jsx b/src/pages/Candidates.tsx similarity index 96% rename from src/pages/Candidates.jsx rename to src/pages/Candidates.tsx index 8d544e9..88fb34a 100644 --- a/src/pages/Candidates.jsx +++ b/src/pages/Candidates.tsx @@ -43,7 +43,9 @@ export default function Candidates() { const sorted = [...applications].sort((a, b) => (b.ai_score || 0) - (a.ai_score || 0)); - const positionOptions = Array.from(new Set(applications.map(resolveTitle).filter(Boolean))).sort(); + /* `new Set(x)` where x is `any[]` infers `Set`; naming the element + type is the whole fix. The values put into it are unchanged. */ + const positionOptions = Array.from(new Set(applications.map(resolveTitle).filter(Boolean))).sort(); const filtered = sorted.filter(a => { const title = resolveTitle(a).toLowerCase(); diff --git a/src/pages/CourseDetail.jsx b/src/pages/CourseDetail.tsx similarity index 100% rename from src/pages/CourseDetail.jsx rename to src/pages/CourseDetail.tsx diff --git a/src/pages/DesignSystem.jsx b/src/pages/DesignSystem.tsx similarity index 99% rename from src/pages/DesignSystem.jsx rename to src/pages/DesignSystem.tsx index c19718f..6e4eed5 100644 --- a/src/pages/DesignSystem.jsx +++ b/src/pages/DesignSystem.tsx @@ -30,8 +30,14 @@ import { AXIS_PROPS, CHART_TONES } from '@/components/ds'; * place. Not linked from the product navigation — reach it at /design-system. */ -/** @param {any} props */ -function Spec({ title, description, children, className = '' }) { +interface SpecProps { + title?: string; + description?: any; + children: React.ReactNode; + className?: string; +} + +function Spec({ title, description, children, className = '' }: SpecProps) { return ( @@ -40,8 +46,12 @@ function Spec({ title, description, children, className = '' }) { ); } -/** @param {any} props */ -function Row({ label = '', children }) { +interface RowProps { + label?: string; + children: React.ReactNode; +} + +function Row({ label = '', children }: RowProps) { return (
{label && {label}} diff --git a/src/pages/EmployeeDashboard.jsx b/src/pages/EmployeeDashboard.tsx similarity index 100% rename from src/pages/EmployeeDashboard.jsx rename to src/pages/EmployeeDashboard.tsx diff --git a/src/pages/HiredHistory.jsx b/src/pages/HiredHistory.tsx similarity index 97% rename from src/pages/HiredHistory.jsx rename to src/pages/HiredHistory.tsx index dc46c8a..f403632 100644 --- a/src/pages/HiredHistory.jsx +++ b/src/pages/HiredHistory.tsx @@ -23,7 +23,12 @@ function formatDate(d) { } catch { return ''; } } -function Stars({ count, size = 14 }) { +interface StarsProps { + count?: any; + size?: any; +} + +function Stars({ count, size = 14 }: StarsProps) { return (
{Array.from({ length: 5 }).map((_, i) => ( @@ -215,8 +220,13 @@ export default function HiredHistory() { ); } -/** @param {any} props */ -function StatPill({ label, value, accent = false }) { +interface StatPillProps { + label?: string; + value?: any; + accent?: any; +} + +function StatPill({ label, value, accent = false }: StatPillProps) { return (
{label} diff --git a/src/pages/KrowIdentity.jsx b/src/pages/KrowIdentity.tsx similarity index 98% rename from src/pages/KrowIdentity.jsx rename to src/pages/KrowIdentity.tsx index f2c989c..73b5c86 100644 --- a/src/pages/KrowIdentity.jsx +++ b/src/pages/KrowIdentity.tsx @@ -177,7 +177,13 @@ export default function KrowIdentity() { ); } -function Chips({ items, onRemove, tone = 'gray' }) { +interface ChipsProps { + items?: any; + onRemove?: (...args: any[]) => void; + tone?: any; +} + +function Chips({ items, onRemove, tone = 'gray' }: ChipsProps) { if (!items?.length) return null; const cls = tone === 'blue' ? 'bg-[#EEF3FE] text-[#1E40AF] border-[#DBEAFE]' : 'bg-[#F3F4F6] text-[#374151]'; return ( diff --git a/src/pages/OpportunityDetail.jsx b/src/pages/OpportunityDetail.tsx similarity index 98% rename from src/pages/OpportunityDetail.jsx rename to src/pages/OpportunityDetail.tsx index c6f911f..b344493 100644 --- a/src/pages/OpportunityDetail.jsx +++ b/src/pages/OpportunityDetail.tsx @@ -42,7 +42,12 @@ function matchedCerts(profile, job) { }); } -function Section({ title, children }) { +interface SectionProps { + title?: string; + children: React.ReactNode; +} + +function Section({ title, children }: SectionProps) { return (

{title}

diff --git a/src/pages/Overview.jsx b/src/pages/Overview.tsx similarity index 98% rename from src/pages/Overview.jsx rename to src/pages/Overview.tsx index 8c3c517..fecc117 100644 --- a/src/pages/Overview.jsx +++ b/src/pages/Overview.tsx @@ -16,7 +16,16 @@ const METRICS = [ -function MetricCard({ icon: Icon, value, label, bg, glow, index }) { +interface MetricCardProps { + icon?: any; + value?: any; + label?: string; + bg?: any; + glow?: any; + index?: number; +} + +function MetricCard({ icon: Icon, value, label, bg, glow, index }: MetricCardProps) { const isYellow = bg === 'bg-[#F9E547]'; return ( { - return new Promise((resolve) => { + return new Promise((resolve) => { if (!ttsEnabled || !window.speechSynthesis) { resolve(); return; } window.speechSynthesis.cancel(); const u = new SpeechSynthesisUtterance(text); @@ -529,8 +533,14 @@ export default function Owliver({ compact = false }) { ); } -/** @param {any} props */ -function SummaryField({ label, value, full = false, empty = '—' }) { +interface SummaryFieldProps { + label?: string; + value?: any; + full?: any; + empty?: any; +} + +function SummaryField({ label, value, full = false, empty = '—' }: SummaryFieldProps) { const has = value && String(value).trim(); return (
@@ -540,7 +550,12 @@ function SummaryField({ label, value, full = false, empty = '—' }) { ); } -function DnaScore({ label, value }) { +interface DnaScoreProps { + label?: string; + value?: any; +} + +function DnaScore({ label, value }: DnaScoreProps) { const v = typeof value === 'number' ? value : 0; const tone = v >= 75 ? 'text-[#16A34A]' : v >= 50 ? 'text-[#0838E0]' : v > 0 ? 'text-[#D97706]' : 'text-[#9CA3AF]'; return ( diff --git a/src/pages/Positions.jsx b/src/pages/Positions.tsx similarity index 100% rename from src/pages/Positions.jsx rename to src/pages/Positions.tsx diff --git a/src/pages/Profile.jsx b/src/pages/Profile.tsx similarity index 100% rename from src/pages/Profile.jsx rename to src/pages/Profile.tsx diff --git a/src/pages/TalentPool.jsx b/src/pages/TalentPool.tsx similarity index 100% rename from src/pages/TalentPool.jsx rename to src/pages/TalentPool.tsx diff --git a/src/pages/WorkerProfile.jsx b/src/pages/WorkerProfile.tsx similarity index 100% rename from src/pages/WorkerProfile.jsx rename to src/pages/WorkerProfile.tsx diff --git a/src/pages/admin/AdminRoute.jsx b/src/pages/admin/AdminRoute.tsx similarity index 100% rename from src/pages/admin/AdminRoute.jsx rename to src/pages/admin/AdminRoute.tsx diff --git a/src/pages/admin/Login.jsx b/src/pages/admin/Login.tsx similarity index 97% rename from src/pages/admin/Login.jsx rename to src/pages/admin/Login.tsx index a3a268f..acc527d 100644 --- a/src/pages/admin/Login.jsx +++ b/src/pages/admin/Login.tsx @@ -33,12 +33,14 @@ export default function AdminLogin() { const [form, setForm] = useState({ email: DEMO_EMAIL, password: '', remember: true }); const [showPassword, setShowPassword] = useState(false); - const [errors, setErrors] = useState(/** @type {{ email?: string, password?: string }} */ ({})); + const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); const [authError, setAuthError] = useState(''); const [status, setStatus] = useState('idle'); const validate = () => { - const next = {}; + /* Same shape the state carries; built empty and filled per failed rule, so + inference alone fixes it at `{}` and neither assignment compiles. */ + const next: { email?: string; password?: string } = {}; if (!form.email.trim()) next.email = 'Enter your work email'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) next.email = 'Enter a valid email address'; // Only "did you type anything". A client-side length rule here would be a diff --git a/src/pages/admin/TalentProfile.jsx b/src/pages/admin/TalentProfile.tsx similarity index 97% rename from src/pages/admin/TalentProfile.jsx rename to src/pages/admin/TalentProfile.tsx index 19f2832..bfb14c2 100644 --- a/src/pages/admin/TalentProfile.jsx +++ b/src/pages/admin/TalentProfile.tsx @@ -1,3 +1,4 @@ +import type { ProfileView } from '@/types/views'; import React, { useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { ArrowLeft, Briefcase, CalendarDays, MapPin, Plus, Sparkles } from 'lucide-react'; @@ -36,7 +37,11 @@ const stageLabel = (application, interviews) => { return 'Applied'; }; -function Facts({ profile }) { +interface FactsProps { + profile: ProfileView; +} + +function Facts({ profile }: FactsProps) { const facts = [ profile.current_position && { icon: Briefcase, label: profile.current_position }, profile.address && { icon: MapPin, label: profile.address }, @@ -56,7 +61,12 @@ function Facts({ profile }) { ); } -function Tags({ title, values = [] }) { +interface TagsProps { + title?: string; + values?: any; +} + +function Tags({ title, values = [] }: TagsProps) { if (!values.length) return null; return (