Custom app launcher, browser home page, and service monitor. cute.haus
homelab react typescript
0

Configure Feed

Select the types of activity you want to include in your feed.

add SearchBar

Aly Raffauf (Jan 29, 2026, 8:10 PM EST) b14ac97b 906188c2

+55 -10
+5
src/App.tsx
··· 2 2 import ServiceGrid from "./components/ServiceGrid"; 3 3 import { HackerNews } from "./components/HackerNews"; 4 4 import { Weather } from "./components/Weather"; 5 + import { SearchBar } from "./components/SearchBar.tsx"; 5 6 import { apps } from "./data/apps"; 6 7 import { privateApps } from "./data/privateApps"; 7 8 import { websites } from "./data/websites"; ··· 9 10 export function App() { 10 11 return ( 11 12 <Layout> 13 + <div className="mb-8"> 14 + <SearchBar /> 15 + </div> 16 + 12 17 <div className="grid gap-4 md:grid-cols-2"> 13 18 <div> 14 19 {" "}
+1 -1
src/Layout.tsx
··· 2 2 3 3 export default function Layout({ children }: { children: React.ReactNode }) { 4 4 return ( 5 - <main className="min-h-screen p-8 max-w-5xl mx-auto"> 5 + <main className="min-h-screen p-8 max-w-6xl mx-auto"> 6 6 {/*<header className="mb-12"> 7 7 <h1 className="text-4xl font-bold">cute.haus</h1> 8 8 </header>*/}
-9
src/components/HackerNews.tsx
··· 1 1 import { useState, useEffect } from "react"; 2 2 3 - function timeAgo(timestamp: number): string { 4 - const seconds = Math.floor(Date.now() / 1000 - timestamp); 5 - 6 - if (seconds < 60) return "1m"; 7 - if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; 8 - if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`; 9 - return `${Math.floor(seconds / 86400)}d`; 10 - } 11 - 12 3 export function HackerNews() { 13 4 const [stories, setStories] = useState<any[]>([]); 14 5
+49
src/components/SearchBar.tsx
··· 1 + import { useState } from "react"; 2 + 3 + const engines = [ 4 + { name: "Google", url: "https://www.google.com/search?q=" }, 5 + { name: "DuckDuckGo", url: "https://duckduckgo.com/?q=" }, 6 + { name: "Kagi", url: "https://kagi.com/search?q=" }, 7 + ]; 8 + 9 + export function SearchBar() { 10 + const [query, setQuery] = useState(""); 11 + const [engine, setEngine] = useState( 12 + engines.find((e) => e.name === "DuckDuckGo")!, 13 + ); 14 + 15 + return ( 16 + <form 17 + className="p-4 bg-zinc-800 border border-zinc-700 rounded-lg" 18 + onSubmit={(e) => { 19 + e.preventDefault(); 20 + if (query.trim()) { 21 + window.location.href = engine.url + encodeURIComponent(query); 22 + } 23 + }} 24 + > 25 + <div className="flex gap-2"> 26 + <select 27 + value={engine.name} 28 + onChange={(e) => 29 + setEngine(engines.find((eng) => eng.name === e.target.value)!) 30 + } 31 + className="px-3 py-2 bg-zinc-900 border border-zinc-600 rounded text-white focus:outline-none focus:border-zinc-500" 32 + > 33 + {engines.map((eng) => ( 34 + <option key={eng.name} value={eng.name}> 35 + {eng.name} 36 + </option> 37 + ))} 38 + </select> 39 + <input 40 + type="text" 41 + value={query} 42 + onChange={(e) => setQuery(e.target.value)} 43 + placeholder="Search..." 44 + className="flex-1 px-3 py-2 bg-zinc-900 border border-zinc-600 rounded text-white placeholder-zinc-500 focus:outline-none focus:border-zinc-500" 45 + /> 46 + </div> 47 + </form> 48 + ); 49 + }