From 603ff23acf5c7a64cb81b2dbc5dc1c2a5a930a27 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Tue, 19 Mar 2024 20:18:08 +0000 Subject: [PATCH] Refactor roadmap search and suggestions --- .../GenerateRoadmap/AITermSuggestionInput.tsx | 145 ++++++++++-------- .../GenerateRoadmap/GenerateRoadmap.tsx | 58 ++++--- .../GenerateRoadmap/RoadmapSearch.tsx | 51 +++--- 3 files changed, 144 insertions(+), 110 deletions(-) diff --git a/src/components/GenerateRoadmap/AITermSuggestionInput.tsx b/src/components/GenerateRoadmap/AITermSuggestionInput.tsx index 7b42f02d0..e54efd0a9 100644 --- a/src/components/GenerateRoadmap/AITermSuggestionInput.tsx +++ b/src/components/GenerateRoadmap/AITermSuggestionInput.tsx @@ -1,16 +1,15 @@ import { + type InputHTMLAttributes, useEffect, useMemo, useRef, useState, - type InputHTMLAttributes, } from 'react'; import { cn } from '../../lib/classname'; import { useOutsideClick } from '../../hooks/use-outside-click'; import { useDebounceValue } from '../../hooks/use-debounce'; import { httpGet } from '../../lib/http'; import { useToast } from '../../hooks/use-toast'; -import { Loader2 } from 'lucide-react'; import { Spinner } from '../ReactIcons/Spinner.tsx'; import type { PageType } from '../CommandMenu/CommandMenu.tsx'; @@ -24,7 +23,7 @@ type GetTopAIRoadmapTermResponse = { type AITermSuggestionInputProps = { value: string; onValueChange: (value: string) => void; - onSelect?: (roadmapId: string) => void; + onSelect?: (roadmapId: string, roadmapTitle: string) => void; inputClassName?: string; wrapperClassName?: string; placeholder?: string; @@ -62,7 +61,7 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) { useState([]); const [searchedText, setSearchedText] = useState(defaultValue); const [activeCounter, setActiveCounter] = useState(0); - const debouncedSearchValue = useDebounceValue(searchedText, 500); + const debouncedSearchValue = useDebounceValue(searchedText, 300); const loadTopAIRoadmapTerm = async () => { const trimmedValue = debouncedSearchValue.trim(); @@ -133,18 +132,23 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) { setIsActive(true); setIsLoading(true); - loadTopAIRoadmapTerm().then((results) => { - const normalizedSearchText = debouncedSearchValue.trim().toLowerCase(); - const matchingOfficialRoadmaps = officialRoadmaps.filter((roadmap) => { - return roadmap.title.toLowerCase().indexOf(normalizedSearchText) !== -1; - }); + loadTopAIRoadmapTerm() + .then((results) => { + const normalizedSearchText = debouncedSearchValue.trim().toLowerCase(); + const matchingOfficialRoadmaps = officialRoadmaps.filter((roadmap) => { + return ( + roadmap.title.toLowerCase().indexOf(normalizedSearchText) !== -1 + ); + }); - setSearchResults( - [...matchingOfficialRoadmaps, ...results]?.slice(0, 5) || [], - ); - setActiveCounter(0); - setIsLoading(false); - }); + setSearchResults( + [...matchingOfficialRoadmaps, ...results]?.slice(0, 5) || [], + ); + setActiveCounter(0); + }) + .finally(() => { + setIsLoading(false); + }); }, [debouncedSearchValue]); useEffect(() => { @@ -158,6 +162,8 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) { setIsActive(false); }); + const isFinishedTyping = debouncedSearchValue === searchedText; + return (
0) { - e.preventDefault(); - const activeData = searchResults[activeCounter]; - if (activeData) { - if (activeData.isOfficial) { - window.location.href = `/${activeData._id}`; - return; - } + if (!searchResults.length || !isFinishedTyping) { + return; + } - onValueChange(activeData.term); - onSelect?.(activeData._id); - setIsActive(false); + e.preventDefault(); + const activeData = searchResults[activeCounter]; + if (activeData) { + if (activeData.isOfficial) { + window.open(`/${activeData._id}`, '_blank')?.focus(); + return; } + + onValueChange(activeData.term); + onSelect?.(activeData._id, activeData.title); + setIsActive(false); } } }} @@ -223,51 +231,54 @@ export function AITermSuggestionInput(props: AITermSuggestionInputProps) {
)} - {isActive && searchResults.length > 0 && searchedText.length > 0 && ( -
-
- {searchResults.map((result, counter) => { - return ( - - ); - })} + + {result.isOfficial ? 'Official' : 'AI Generated'} + + {result?.title || result?.term} + + ); + })} +
- - )} + )} ); } diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.tsx b/src/components/GenerateRoadmap/GenerateRoadmap.tsx index c69db6291..62f39ef7a 100644 --- a/src/components/GenerateRoadmap/GenerateRoadmap.tsx +++ b/src/components/GenerateRoadmap/GenerateRoadmap.tsx @@ -37,6 +37,7 @@ import { AIRoadmapAlert } from './AIRoadmapAlert.tsx'; import { OpenAISettings } from './OpenAISettings.tsx'; import { IS_KEY_ONLY_ROADMAP_GENERATION } from '../../lib/ai.ts'; import { AITermSuggestionInput } from './AITermSuggestionInput.tsx'; +import { useParams } from '../../hooks/use-params.ts'; export type GetAIRoadmapLimitResponse = { used: number; @@ -91,6 +92,7 @@ export function GenerateRoadmap() { const [hasSubmitted, setHasSubmitted] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [isLoadingResults, setIsLoadingResults] = useState(false); const [roadmapTerm, setRoadmapTerm] = useState(''); const [generatedRoadmapContent, setGeneratedRoadmapContent] = useState(''); const [currentRoadmap, setCurrentRoadmap] = @@ -194,7 +196,7 @@ export function GenerateRoadmap() { const handleSubmit = async (e: FormEvent) => { e.preventDefault(); - if (!roadmapTerm) { + if (!roadmapTerm || isLoadingResults) { return; } @@ -481,7 +483,7 @@ export function GenerateRoadmap() { > {roadmapLimitUsed} of {roadmapLimit} {' '} - roadmaps generated. + roadmaps generated today. {!openAPIKey && (