2026-01-31 16:42:33 +00:00

131 lines
4.8 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useAppSelector } from '../stores/hooks';
import axios from 'axios';
import Link from 'next/link';
const Search = () => {
const router = useRouter();
const focusRing = useAppSelector((state) => state.style.focusRingColor);
const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const [autocompleteResults, setAutocompleteResults] = useState([]);
const [showDropdown, setShowDropdown] = useState(false);
const dropdownRef = useRef(null);
const validateSearch = (value) => {
let error;
if (!value) {
error = 'Required';
} else if (value.length < 2) {
error = 'Minimum length: 2 characters';
}
return error;
};
const handleAutocomplete = async (query) => {
if (query.length < 2) {
setAutocompleteResults([]);
setShowDropdown(false);
return;
}
try {
const response = await axios.get(`/search/autocomplete?query=${query}`);
setAutocompleteResults(response.data);
setShowDropdown(response.data.length > 0);
} catch (error) {
console.error('Autocomplete error:', error);
}
};
useEffect(() => {
const handleClickOutside = (event) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setShowDropdown(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
return (
<div className="relative" ref={dropdownRef}>
<Formik
initialValues={{
search: '',
}}
onSubmit={(values, { setSubmitting, resetForm }) => {
router.push(`/search?query=${values.search}`);
resetForm();
setSubmitting(false);
setShowDropdown(false);
}}
validateOnBlur={false}
validateOnChange={false}
>
{({ errors, touched, values, setFieldValue, submitForm }) => (
<Form style={{width: '300px'}} autoComplete="off">
<Field
id='search'
name='search'
validate={validateSearch}
placeholder='Search'
className={` ${corners} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-2 relative ml-2 w-full dark:placeholder-dark-600 ${focusRing} shadow-none`}
onChange={(e) => {
const value = e.target.value;
setFieldValue('search', value);
handleAutocomplete(value);
}}
onFocus={() => {
if (autocompleteResults.length > 0) setShowDropdown(true);
}}
/>
{errors.search && touched.search && values.search.length < 2 ? (
<div className='text-red-500 text-sm ml-2 absolute'>{errors.search}</div>
) : null}
{showDropdown && (
<div className={`absolute z-50 w-full ml-2 mt-1 bg-white dark:bg-dark-800 border dark:border-dark-700 shadow-lg ${corners} overflow-hidden`}>
{autocompleteResults.map((result) => (
<div
key={`${result.type}-${result.id}`}
className="p-3 hover:bg-gray-100 dark:hover:bg-dark-700 cursor-pointer border-b dark:border-dark-700 last:border-0"
onClick={() => {
if (result.type === 'product') {
router.push(`/products/${result.id}`);
} else {
router.push(`/categories/${result.id}`);
}
setShowDropdown(false);
setFieldValue('search', '');
}}
>
<div className="flex justify-between items-center">
<div>
<span className="font-semibold text-sm block">{result.title || result.name}</span>
<span className="text-xs text-gray-500 capitalize">{result.type}</span>
</div>
{result.price && (
<span className="text-green-600 font-bold text-sm">${result.price}</span>
)}
</div>
</div>
))}
<div
className="p-2 text-center text-blue-600 text-xs font-semibold hover:bg-gray-50 dark:hover:bg-dark-700 cursor-pointer"
onClick={() => submitForm()}
>
View all results for &quot;{values.search}&quot;
</div>
</div>
)}
</Form>
)}
</Formik>
</div>
);
};
export default Search;