131 lines
4.8 KiB
TypeScript
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 "{values.search}"
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Form>
|
|
)}
|
|
</Formik>
|
|
</div>
|
|
);
|
|
};
|
|
export default Search; |