| 1 | import { ChevronsUpDownIcon, Loader2, SearchIcon } from "lucide-react"; |
| 2 | import Image from "next/image"; |
| 3 | |
| 4 | import { Button } from "@/components/ui/button"; |
| 5 | import { |
| 6 | Collapsible, |
| 7 | CollapsibleContent, |
| 8 | CollapsibleTrigger, |
| 9 | } from "@/components/ui/collapsible"; |
| 10 | |
| 11 | export interface SearchResult { |
| 12 | url: string; |
| 13 | title: string; |
| 14 | published_date: string; |
| 15 | content: string; |
| 16 | } |
| 17 | |
| 18 | export function Searching({ query }: { query: string }) { |
| 19 | return ( |
| 20 | <div className="mb-2 w-full rounded-lg border border-primary/20 bg-background"> |
| 21 | <div className="flex h-12 items-center gap-3 px-4 text-muted-foreground"> |
| 22 | <Loader2 className="size-5 animate-spin" /> |
| 23 | <div className="min-w-0 flex-1"> |
| 24 | <p className="truncate text-sm font-medium"> |
| 25 | Searching the web for "{query}" |
| 26 | </p> |
| 27 | </div> |
| 28 | </div> |
| 29 | </div> |
| 30 | ); |
| 31 | } |
| 32 | |
| 33 | export function Searched({ |
| 34 | results, |
| 35 | query, |
| 36 | }: { |
| 37 | results: SearchResult[]; |
| 38 | query: string; |
| 39 | }) { |
| 40 | return ( |
| 41 | <Collapsible className="mb-2 w-full rounded-lg border border-primary/20 bg-background"> |
| 42 | <CollapsibleTrigger asChild> |
| 43 | <Button variant="ghost" className="h-12 w-full justify-between px-4"> |
| 44 | <div className="flex w-[90%] min-w-0 items-center gap-3"> |
| 45 | <SearchIcon className="size-5 shrink-0" /> |
| 46 | <div className="flex min-w-0 flex-col items-start overflow-hidden"> |
| 47 | <span className="w-full truncate text-sm font-medium"> |
| 48 | {query} |
| 49 | </span> |
| 50 | <span className="text-xs text-muted-foreground"> |
| 51 | {results.length} result{results.length === 1 ? "" : "s"} found |
| 52 | </span> |
| 53 | </div> |
| 54 | </div> |
| 55 | <ChevronsUpDownIcon className="size-5 shrink-0 transition-transform duration-300 data-[state=open]:rotate-180" /> |
| 56 | </Button> |
| 57 | </CollapsibleTrigger> |
| 58 | <CollapsibleContent className="grid max-w-full grid-cols-1 gap-y-2 p-4"> |
| 59 | {results.map((result) => { |
| 60 | let domain = "allweone.com"; |
| 61 | try { |
| 62 | domain = new URL(result.url || "https://allweone.com").hostname; |
| 63 | } catch { |
| 64 | domain = "allweone.com"; |
| 65 | } |
| 66 | const faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`; |
| 67 | |
| 68 | return ( |
| 69 | <div |
| 70 | key={result.url || `${query}-${result.title}`} |
| 71 | className="flex max-w-full items-start gap-3 rounded-lg border border-primary/20 p-3" |
| 72 | > |
| 73 | <Image |
| 74 | unoptimized |
| 75 | width={32} |
| 76 | height={32} |
| 77 | src={faviconUrl} |
| 78 | alt={domain} |
| 79 | className="mt-1 size-4" |
| 80 | /> |
| 81 | <div className="min-w-0 flex-1 overflow-hidden"> |
| 82 | <h4 className="truncate text-sm font-medium"> |
| 83 | {result.title} |
| 84 | </h4> |
| 85 | <p className="line-clamp-2 text-xs text-muted-foreground"> |
| 86 | {result.content} |
| 87 | </p> |
| 88 | {result.url ? ( |
| 89 | <a |
| 90 | href={result.url} |
| 91 | target="_blank" |
| 92 | rel="noopener noreferrer" |
| 93 | className="block truncate text-xs text-primary hover:underline" |
| 94 | > |
| 95 | {result.url} |
| 96 | </a> |
| 97 | ) : null} |
| 98 | </div> |
| 99 | </div> |
| 100 | ); |
| 101 | })} |
| 102 | </CollapsibleContent> |
| 103 | </Collapsible> |
| 104 | ); |
| 105 | } |
| 106 |