11import React , { useState , useEffect , useRef } from 'react' ;
22import { Link } from 'react-router-dom' ;
33import { MagnifyingGlassIcon } from '@phosphor-icons/react' ;
4+ import useSearchableData from '../hooks/useSearchableData' ;
5+ import { filterItems } from '../utils/search' ;
46
57const Search = ( { isVisible } ) => {
68 const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
79 const [ searchResults , setSearchResults ] = useState ( [ ] ) ;
810 const [ isDropdownOpen , setIsDropdownOpen ] = useState ( false ) ;
9- const [ data , setData ] = useState ( {
10- posts : [ ] ,
11- projects : [ ] ,
12- logs : [ ] ,
13- routes : [ ] ,
14- apps : [ ] ,
15- } ) ;
11+ const { items, isLoading } = useSearchableData ( ) ;
1612 const searchRef = useRef ( null ) ;
1713 const inputRef = useRef ( null ) ;
1814
@@ -22,119 +18,16 @@ const Search = ({ isVisible }) => {
2218 }
2319 } , [ isVisible ] ) ;
2420
25- useEffect ( ( ) => {
26- const fetchData = async ( ) => {
27- try {
28- const [ postsRes , projectsRes , logsRes , appsRes ] = await Promise . all ( [
29- fetch ( '/posts/posts.json' ) ,
30- fetch ( '/projects/projects.json' ) ,
31- fetch ( '/logs/logs.json' ) ,
32- fetch ( '/apps/apps.json' ) ,
33- ] ) ;
34-
35- const posts = await postsRes . json ( ) ;
36- const projects = await projectsRes . json ( ) ;
37- const logs = await logsRes . json ( ) ;
38- const appsData = await appsRes . json ( ) ;
39- const allApps = Object . values ( appsData ) . flatMap (
40- ( category ) => category . apps ,
41- ) ;
42-
43- const allPosts = posts . flatMap ( ( item ) =>
44- item . series
45- ? item . series . posts . map ( ( p ) => ( { ...p , series : item . title } ) )
46- : item ,
47- ) ;
48-
49- // Manually define common routes
50- const routes = [
51- { title : 'Home' , slug : '/' , type : 'route' } ,
52- { title : 'Blogs' , slug : '/blog' , type : 'route' } ,
53- { title : 'Projects' , slug : '/projects' , type : 'route' } ,
54- { title : 'About Me' , slug : '/about' , type : 'route' } ,
55- { title : 'Logs' , slug : '/logs' , type : 'route' } ,
56- { title : 'Settings' , slug : '/settings' , type : 'route' } ,
57- { title : 'Dungeons and Dragons' , slug : '/stories' , type : 'route' } ,
58- { title : 'Stories' , slug : '/stories' , type : 'route' } ,
59- { title : 'From Serfs and Frauds' , slug : '/stories' , type : 'route' } ,
60- { title : 'Apps' , slug : '/apps' , type : 'route' } ,
61- { title : 'Random' , slug : '/random' , type : 'route' } ,
62- ] ;
63-
64- setData ( {
65- posts : allPosts ,
66- projects : projects ,
67- logs : logs ,
68- routes : routes ,
69- apps : allApps ,
70- } ) ; // Include routes in data
71- } catch ( error ) {
72- console . error ( 'Failed to fetch search data:' , error ) ;
73- }
74- } ;
75-
76- fetchData ( ) ;
77- } , [ ] ) ;
78-
7921 useEffect ( ( ) => {
8022 if ( searchTerm ) {
81- const lowerCaseSearchTerm = searchTerm . toLowerCase ( ) ;
82- const posts = data . posts
83- . filter (
84- ( post ) =>
85- post . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ||
86- post . tags ?. some ( ( tag ) =>
87- tag . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ,
88- ) ,
89- )
90- . map ( ( post ) => ( { ...post , type : 'post' } ) ) ;
91-
92- const projects = data . projects
93- . filter (
94- ( project ) =>
95- project . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ||
96- project . technologies ?. some ( ( tech ) =>
97- tech . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ,
98- ) ,
99- )
100- . map ( ( project ) => ( { ...project , type : 'project' } ) ) ;
101-
102- const logs = data . logs
103- . filter (
104- ( log ) =>
105- log . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ||
106- log . category ?. toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ||
107- log . tags ?. some ( ( tag ) =>
108- tag . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ,
109- ) ,
110- )
111- . map ( ( log ) => ( { ...log , type : 'log' } ) ) ;
112-
113- // Filter routes
114- const routes = data . routes
115- . filter (
116- ( route ) =>
117- route . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ||
118- route . slug . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ,
119- )
120- . map ( ( route ) => ( { ...route , type : 'route' } ) ) ;
121-
122- // Filter apps
123- const apps = data . apps
124- . filter (
125- ( app ) =>
126- app . title . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ||
127- app . slug . toLowerCase ( ) . includes ( lowerCaseSearchTerm ) ,
128- )
129- . map ( ( app ) => ( { ...app , type : 'app' } ) ) ;
130-
131- setSearchResults ( [ ...posts , ...projects , ...logs , ...routes , ...apps ] ) ; // Include routes in search results
23+ const results = filterItems ( items , searchTerm ) ;
24+ setSearchResults ( results ) ;
13225 setIsDropdownOpen ( true ) ;
13326 } else {
13427 setSearchResults ( [ ] ) ;
13528 setIsDropdownOpen ( false ) ;
13629 }
137- } , [ searchTerm , data ] ) ;
30+ } , [ searchTerm , items ] ) ;
13831
13932 useEffect ( ( ) => {
14033 const handleClickOutside = ( event ) => {
@@ -150,20 +43,7 @@ const Search = ({ isVisible }) => {
15043 } , [ ] ) ;
15144
15245 const getResultLink = ( result ) => {
153- switch ( result . type ) {
154- case 'post' :
155- return `/blog/${ result . slug } ` ;
156- case 'project' :
157- return `/projects/${ result . slug } ` ;
158- case 'log' :
159- return `/logs/${ result . slug } ` ;
160- case 'route' : // Handle routes
161- return result . slug ;
162- case 'app' : // Handle apps
163- return `${ result . to } ` ;
164- default :
165- return '/' ;
166- }
46+ return result . path || '/' ;
16747 } ;
16848
16949 if ( ! isVisible ) {
@@ -182,11 +62,12 @@ const Search = ({ isVisible }) => {
18262 < input
18363 ref = { inputRef }
18464 type = "text"
185- placeholder = " Search..."
65+ placeholder = { isLoading ? "Loading..." : " Search..."}
18666 value = { searchTerm }
18767 onChange = { ( e ) => setSearchTerm ( e . target . value ) }
18868 onFocus = { ( ) => setIsDropdownOpen ( true ) }
18969 className = "bg-gray-800 text-white w-full py-2 px-4 pl-10 focus:outline-none focus:ring-2 focus:ring-primary-400 rounded-md"
70+ disabled = { isLoading }
19071 />
19172 < MagnifyingGlassIcon className = "absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
19273 { isDropdownOpen && searchResults . length > 0 && (
0 commit comments