Fork of atp.tools as a universal profile for people on the ATmosphere
at main 157 lines 5.1 kB view raw
1import { Input } from "@/components/ui/input"; 2import { useState, useEffect } from "react"; 3import { Search, X } from "lucide-react"; 4import { useNavigate } from "@tanstack/react-router"; 5import { KbdKey } from "./ui/kbdKey"; 6import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; 7import { Button } from "./ui/button"; 8 9// function determineRouteType(input: string) { 10// if (input.startsWith("at://")) { 11// return "at url"; 12// } else if (input.startsWith("https://")) { 13// return "pds"; 14// } else { 15// return "handle"; 16// } 17// return "unknown"; 18// } 19 20export function isOnMac() { 21 return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0; 22} 23 24function isMobile() { 25 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 26 navigator.userAgent, 27 ); 28} 29 30export function SmartSearchBar({ 31 isKeybindEnabled = false, 32}: { 33 isKeybindEnabled?: boolean; 34}) { 35 const navigate = useNavigate(); 36 const [input, setInput] = useState(""); 37 const [open, setOpen] = useState(false); 38 39 const handleSubmit = (e: React.FormEvent) => { 40 e.preventDefault(); 41 const value = input.trim(); 42 if (value && value !== "@") { 43 // replace at:// with / to match the route correctly 44 if (value.includes("bsky.app") || value.includes("main.bsky.dev")) { 45 try { 46 const url = new URL(value); 47 const pathParts = url.pathname.split("/"); 48 49 if (pathParts[1] === "profile") { 50 const handle = pathParts[2]; 51 52 if (pathParts[3] === "post") { 53 // Post URL: /profile/handle/post/postid 54 navigate({ 55 to: `/at:/${handle}/app.bsky.feed.post/${pathParts[4]}`, 56 }); 57 } else if (pathParts[3] === "lists") { 58 // List URL: /profile/handle/lists/listid 59 navigate({ 60 to: `/at:/${handle}/app.bsky.graph.list/${pathParts[4]}`, 61 }); 62 } else { 63 // Profile URL: /profile/handle 64 navigate({ 65 to: `/at:/${handle}`, 66 }); 67 } 68 } 69 } catch (e) { 70 console.error("Invalid Bluesky URL:", e); 71 } 72 } else if (value.startsWith("pds/") || value.startsWith("https:/")) { 73 navigate({ 74 to: "/pds/$url", 75 params: { 76 url: value.replace("https:/", "").replace("pds/", ""), 77 }, 78 }); 79 } else if (value === "typing") { 80 navigate({ to: "/rnfgrertt/typing" }); 81 } else if (value === "pdsls") { 82 navigate({ to: "/rnfgrertt/pdsls" }); 83 } else if (value === "borgle" || value === "wordle") { 84 navigate({ to: "/rnfgrertt/borgle" }); 85 } else { 86 // Allow handle lookups to start with @ 87 navigate({ 88 to: `/at:/${value.replace("at:/", "").replace(/^@/, "")}`, 89 }); 90 } 91 setOpen(false); 92 } 93 }; 94 95 useEffect(() => { 96 if (isMobile()) return; 97 const handleKeyDown = (e: KeyboardEvent) => { 98 if ( 99 (e.metaKey || e.ctrlKey) && 100 e.key === "k" && 101 isKeybindEnabled === true 102 ) { 103 e.preventDefault(); 104 setOpen(true); 105 } 106 }; 107 108 document.addEventListener("keydown", handleKeyDown); 109 return () => document.removeEventListener("keydown", handleKeyDown); 110 }, []); 111 112 return ( 113 <Dialog open={open} onOpenChange={setOpen}> 114 <DialogTrigger asChild> 115 <Button 116 onClick={() => setOpen(true)} 117 variant="outline" 118 className="relative w-full justify-start text-muted-foreground" 119 > 120 <Search className="mr-2 h-4 w-4" /> 121 <span className="flex-1 text-left">Search...</span> 122 {!isMobile() && ( 123 <div className="md:flex items-center -mr-2 hidden"> 124 <KbdKey keys={[isOnMac() ? "cmd" : "ctrl", "k"]} /> 125 </div> 126 )} 127 </Button> 128 </DialogTrigger> 129 130 <DialogContent className="sm:max-w-[800px] p-0 border-0 rounded-full bg-transparent"> 131 <form 132 onSubmit={handleSubmit} 133 className="relative backdrop-blur-3xl rounded-full" 134 > 135 <Search className="absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" /> 136 <Input 137 type="text" 138 placeholder="Enter a handle, at url, or PDS url" 139 value={input} 140 onChange={(e) => setInput(e.currentTarget.value)} 141 className="border focus-visible:ring-0 focus-visible:ring-offset-0 pl-12 py-6 text-lg rounded-xl shadow-lg dark:shadow-slate-950" 142 autoCorrect="off" 143 autoCapitalize="none" 144 autoFocus 145 /> 146 <div className="absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-x-2"> 147 {!isMobile() && <KbdKey keys={["esc"]} />} 148 <X 149 className="h-4 w-4 text-muted-foreground" 150 onClick={() => setOpen(false)} 151 /> 152 </div> 153 </form> 154 </DialogContent> 155 </Dialog> 156 ); 157}