Merge pull request #69 from rede5/codex/implement-search-functionality

fix(frontend): sync /jobs filters to URL and fix search layout
This commit is contained in:
Tiago Yamamoto 2026-02-17 18:14:15 -03:00 committed by GitHub
commit e6d22b87c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,6 @@
"use client" "use client"
import { useSearchParams } from "next/navigation" import { usePathname, useRouter, useSearchParams } from "next/navigation"
import { useEffect, useState, useMemo, Suspense, useCallback } from "react" import { useEffect, useState, useMemo, Suspense, useCallback } from "react"
import { Navbar } from "@/components/navbar" import { Navbar } from "@/components/navbar"
import { Footer } from "@/components/footer" import { Footer } from "@/components/footer"
@ -19,8 +19,13 @@ import { Search, MapPin, Briefcase, SlidersHorizontal, X, ArrowUpDown, Clock, Be
import { motion, AnimatePresence } from "framer-motion" import { motion, AnimatePresence } from "framer-motion"
import type { Job } from "@/lib/types" import type { Job } from "@/lib/types"
const WORK_MODE_OPTIONS = ["remote", "hybrid", "onsite"]
const TYPE_OPTIONS = ["full-time", "part-time", "contract", "dispatch"]
function JobsContent() { function JobsContent() {
const { t } = useTranslation() const { t } = useTranslation()
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams() const searchParams = useSearchParams()
const { searches, saveSearch, clearSearches, removeSearch } = useRecentSearches() const { searches, saveSearch, clearSearches, removeSearch } = useRecentSearches()
@ -55,6 +60,17 @@ function JobsContent() {
const debouncedLocation = useDebounce(locationFilter, 500) const debouncedLocation = useDebounce(locationFilter, 500)
const handleSearch = useCallback(() => { const handleSearch = useCallback(() => {
const query = new URLSearchParams()
if (searchTerm.trim()) query.set("q", searchTerm.trim())
if (locationFilter.trim()) query.set("location", locationFilter.trim())
if (typeFilter !== "all") query.set("type", typeFilter)
if (workModeFilter !== "all") query.set("workMode", workModeFilter)
if (salaryMin.trim()) query.set("salary", salaryMin.trim())
const nextUrl = query.toString() ? `${pathname}?${query.toString()}` : pathname
router.replace(nextUrl, { scroll: false })
if (searchTerm || locationFilter) { if (searchTerm || locationFilter) {
saveSearch({ saveSearch({
query: searchTerm, query: searchTerm,
@ -66,7 +82,17 @@ function JobsContent() {
}, },
}) })
} }
}, [searchTerm, locationFilter, typeFilter, workModeFilter, datePostedFilter, saveSearch]) }, [
searchTerm,
locationFilter,
typeFilter,
workModeFilter,
datePostedFilter,
salaryMin,
pathname,
router,
saveSearch,
])
// Initial params // Initial params
useEffect(() => { useEffect(() => {
@ -74,10 +100,17 @@ function JobsContent() {
const q = searchParams.get("q") const q = searchParams.get("q")
const s = searchParams.get("s") const s = searchParams.get("s")
const type = searchParams.get("type") const type = searchParams.get("type")
const contractType = searchParams.get("contractType")
const location = searchParams.get("location") const location = searchParams.get("location")
const l = searchParams.get("l") const l = searchParams.get("l")
const mode = searchParams.get("mode") const mode = searchParams.get("mode")
const workMode = searchParams.get("workMode") const workMode = searchParams.get("workMode")
const salary = searchParams.get("salary")
const salaryMinParam = searchParams.get("salaryMin")
const rawType = (type || contractType || "").toLowerCase()
const rawWorkMode = (mode || workMode || "").toLowerCase()
const parsedSalary = (salaryMinParam || salary || "").replace(/[^\d.,]/g, "").replace(",", ".")
if (tech || q || s) { if (tech || q || s) {
setSearchTerm(tech || q || s || "") setSearchTerm(tech || q || s || "")
@ -89,11 +122,21 @@ function JobsContent() {
setShowFilters(true) setShowFilters(true)
} }
if (type === "remote") { if (rawType === "remote") {
setWorkModeFilter("remote") setWorkModeFilter("remote")
setShowFilters(true) setShowFilters(true)
} else if (mode || workMode) { } else if (rawWorkMode && WORK_MODE_OPTIONS.includes(rawWorkMode)) {
setWorkModeFilter(mode || workMode || "all") setWorkModeFilter(rawWorkMode)
setShowFilters(true)
}
if (rawType && TYPE_OPTIONS.includes(rawType)) {
setTypeFilter(rawType)
setShowFilters(true)
}
if (parsedSalary) {
setSalaryMin(parsedSalary)
setShowFilters(true) setShowFilters(true)
} }
}, [searchParams]) }, [searchParams])
@ -194,8 +237,8 @@ function JobsContent() {
} }
// Hardcoded options since we don't have all data client-side // Hardcoded options since we don't have all data client-side
const workModeOptions = ["remote", "hybrid", "onsite"] const workModeOptions = WORK_MODE_OPTIONS
const typeOptions = ["full-time", "part-time", "contract", "dispatch"] const typeOptions = TYPE_OPTIONS
return ( return (
<> <>
@ -264,7 +307,7 @@ function JobsContent() {
/> />
</div> </div>
<div className="flex gap-2"> <div className="flex flex-wrap gap-2 lg:flex-nowrap">
<Button <Button
onClick={handleSearch} onClick={handleSearch}
className="h-12 gap-2 bg-[#F0932B] hover:bg-[#E8821C] text-white" className="h-12 gap-2 bg-[#F0932B] hover:bg-[#E8821C] text-white"