diff --git a/docs/superpowers/plans/2026-03-22-frontend-replacement.md b/docs/superpowers/plans/2026-03-22-frontend-replacement.md deleted file mode 100644 index 1cb475e..0000000 --- a/docs/superpowers/plans/2026-03-22-frontend-replacement.md +++ /dev/null @@ -1,358 +0,0 @@ -# Frontend Replacement Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Replace the Go template-based frontend with the search-zen-50 React SPA, embedded in the Go binary as a single deployment. - -**Architecture:** Build React app → embed in Go binary via `//go:embed` → serve via Go HTTP server with SPA fallback routing. React calls `/search?format=json` and `/autocompleter?q=` APIs. - -**Tech Stack:** Go (embed), React 18, Vite, TailwindCSS, React Router, @tanstack/react-query - ---- - -## File Map - -| File | Action | -|------|--------| -| `cmd/kafka/main.go` | Modify - replace template handlers with SPA handler | -| `internal/spa/spa.go` | Create - embed React build, serve static files, SPA fallback | -| `internal/spa/dist/` | Build output - React build artifacts (gitignored) | -| `src/hooks/use-search.ts` | Modify - replace mock with real API calls | -| `src/lib/mock-data.ts` | Keep types, remove MOCK_RESPONSE usage | - ---- - -## Task 1: Build React App - -**Files:** -- Build: `/tmp/search-zen-50/dist/` (output directory) - -- [ ] **Step 1: Install dependencies and build** - -```bash -cd /tmp/search-zen-50 && bun install && bun run build -``` - -Expected: `dist/` directory created with `index.html`, `assets/` folder containing JS/CSS bundles - -- [ ] **Step 2: Verify dist contents** - -```bash -ls /tmp/search-zen-50/dist/ && ls /tmp/search-zen-50/dist/assets/ | head -10 -``` - -Expected: `index.html` exists, `assets/` contains `.js` and `.css` files - ---- - -## Task 2: Create SPA Go Package - -**Files:** -- Create: `internal/spa/spa.go` - -```go -package spa - -import ( - "embed" - "io/fs" - "net/http" - "path" -) - -//go:embed all:dist -var distFS embed.FS - -// DistFS returns the embedded dist directory as an fs.FS. -func DistFS() (fs.FS, error) { - return fs.Sub(distFS, "dist") -} - -// NewHandler returns an HTTP handler that: -// - Serves static files from the embedded dist/ directory -// - Falls back to index.html for SPA routing (any non-API path) -func NewHandler() http.Handler { - dist, err := DistFS() - if err != nil { - panic("spa: embedded dist not found: " + err.Error()) - } - return &spaHandler{dist: dist} -} - -type spaHandler struct { - dist fs.FS -} - -func (h *spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // API paths are handled by Go API handlers - this should never be reached - // since Go mux dispatches to specific handlers first. But if reached, - // pass through to FileServer which will return 404 for unknown paths. - - // Try to serve the requested file first - filePath := path.Clean(r.URL.Path) - f, err := h.dist.Open(filePath) - if err == nil { - f.Close() - // File exists - serve it via FileServer - http.FileServer(http.FS(h.dist)).ServeHTTP(w, r) - return - } - - // Fallback to index.html for SPA routing - indexFile, err := h.dist.Open("index.html") - if err != nil { - http.Error(w, "index.html not found in embedded files", http.StatusInternalServerError) - return - } - indexFile.Close() - http.FileServer(http.FS(h.dist)).ServeHTTP(w, r) -} - -``` - ---- - -## Task 3: Wire SPA Handler in main.go - -**Files:** -- Modify: `cmd/kafka/main.go` - -- [ ] **Step 1: Replace handlers with SPA** - -In `main.go`, find and replace the `mux.HandleFunc` section (lines 82-88) and the static file serving section (lines 90-96). - -Old code (lines 82-96): -```go -mux := http.NewServeMux() -mux.HandleFunc("/", h.Index) -mux.HandleFunc("/healthz", h.Healthz) -mux.HandleFunc("/search", h.Search) -mux.HandleFunc("/autocompleter", h.Autocompleter) -mux.HandleFunc("/preferences", h.Preferences) -mux.HandleFunc("/opensearch.xml", h.OpenSearch(cfg.Server.BaseURL)) - -// Serve embedded static files (CSS, JS, images). -staticFS, err := views.StaticFS() -if err != nil { - log.Fatalf("failed to load static files: %v", err) -} -var subFS fs.FS = staticFS -mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(subFS)))) -``` - -New code: -```go -mux := http.NewServeMux() - -// API routes - handled by Go -mux.HandleFunc("/healthz", h.Healthz) -mux.HandleFunc("/search", h.Search) -mux.HandleFunc("/autocompleter", h.Autocompleter) -mux.HandleFunc("/opensearch.xml", h.OpenSearch(cfg.Server.BaseURL)) - -// SPA handler - serves React app for all other routes -spaHandler := spa.NewHandler() -mux.Handle("/", spaHandler) -``` - -- [ ] **Step 2: Add spa import** - -Add to imports (after `"github.com/metamorphosis-dev/kafka/internal/search"`): -```go -"github.com/metamorphosis-dev/kafka/internal/spa" -``` - -- [ ] **Step 3: Remove unused views import if needed** - -If `views` is only used for `StaticFS()`, remove the import. The template rendering functions (`RenderIndex`, etc.) won't be needed anymore. - -- [ ] **Step 4: Verify build** - -```bash -cd /home/ashie/git/kafka && go build ./cmd/kafka/ -``` - -Expected: Builds successfully (may fail on embed if dist not found - continue to next task) - ---- - -## Task 4: Wire React to Real API - -**Files:** -- Modify: `src/hooks/use-search.ts` in `/tmp/search-zen-50/` - -- [ ] **Step 1: Replace mock search with real API call** - -Replace the `search` function in `use-search.ts`: - -Old code (lines 23-36): -```typescript -const search = useCallback(async (query: string) => { - if (!query.trim()) return; - - setState((prev) => ({ ...prev, query, isLoading: true, error: null, hasSearched: true })); - - // Simulate network delay - await new Promise((r) => setTimeout(r, 800)); - - setState((prev) => ({ - ...prev, - isLoading: false, - results: { ...MOCK_RESPONSE, query }, - })); -}, []); -``` - -New code: -```typescript -const search = useCallback(async (query: string) => { - if (!query.trim()) return; - - setState((prev) => ({ ...prev, query, isLoading: true, error: null, hasSearched: true })); - - try { - const response = await fetch(`/search?format=json&q=${encodeURIComponent(query)}`); - if (!response.ok) { - throw new Error(`HTTP ${response.status}`); - } - const data = await response.json(); - setState((prev) => ({ - ...prev, - isLoading: false, - results: data, - })); - } catch (err) { - setState((prev) => ({ - ...prev, - isLoading: false, - error: err instanceof Error ? err.message : "Search failed", - })); - } -}, []); -``` - -- [ ] **Step 2: Remove mock data import** - -Remove the mock import line (should be near line 2): -```typescript -import { MOCK_RESPONSE, type SearXNGResponse, type Category } from "@/lib/mock-data"; -``` - -Replace with: -```typescript -import type { SearXNGResponse, Category } from "@/lib/mock-data"; -``` - -- [ ] **Step 3: Keep the CATEGORIES export** - -Ensure `mock-data.ts` still exports `CATEGORIES` and `Category` type. The file should look like: - -```typescript -// Keep these exports - used by CategoryTabs and preferences -export const CATEGORIES = ["general", "it", "images", "news"] as const; -export type Category = typeof CATEGORIES[number]; - -// Keep interfaces -export interface SearchResult { - url: string; - title: string; - content: string; - engine: string; - parsed_url: [string, string, string, string, string]; - engines: string[]; - positions: number[]; - score: number; - category: string; - pretty_url: string; - img_src?: string; - thumbnail?: string; - publishedDate?: string; -} - -export interface SearXNGResponse { - query: string; - number_of_results: number; - results: SearchResult[]; - answers: string[]; - corrections: string[]; - infoboxes: any[]; - suggestions: string[]; - unresponsive_engines: string[]; -} -``` - ---- - -## Task 5: Rebuild React and Verify - -**Files:** -- Build: `/tmp/search-zen-50/dist/` - -- [ ] **Step 1: Rebuild with changes** - -```bash -cd /tmp/search-zen-50 && bun run build -``` - -- [ ] **Step 2: Copy dist to kafka** - -```bash -rm -rf /home/ashie/git/kafka/internal/spa/dist -cp -r /tmp/search-zen-50/dist /home/ashie/git/kafka/internal/spa/dist -``` - -- [ ] **Step 3: Verify Go build** - -```bash -cd /home/ashie/git/kafka && go build ./cmd/kafka/ && echo "Build successful" -``` - -Expected: "Build successful" - ---- - -## Task 6: Test the Integration - -- [ ] **Step 1: Start the server** - -```bash -cd /home/ashie/git/kafka && ./kafka -config config.toml & -sleep 2 -``` - -- [ ] **Step 2: Test homepage** - -```bash -curl -s http://localhost:8080/ | head -20 -``` - -Expected: HTML with `
` from React app - -- [ ] **Step 3: Test API** - -```bash -curl -s "http://localhost:8080/search?format=json&q=test" | head -50 -``` - -Expected: JSON search response - -- [ ] **Step 4: Clean up** - -```bash -pkill -f "./kafka" 2>/dev/null; echo "Done" -``` - ---- - -## Dependencies - -- Node.js/Bun for building React app -- Go 1.24+ for embed functionality -- No new Go dependencies - -## Notes - -- The `internal/spa/dist/` folder should be gitignored (build artifact) -- The `internal/spa/dist/` copy is needed for the embed to work at compile time -- Preferences page is entirely client-side (localStorage) - no backend needed -- Autocomplete can be added later by modifying `SearchInput.tsx` to call `/autocompleter` diff --git a/docs/superpowers/specs/2026-03-22-frontend-replacement-design.md b/docs/superpowers/specs/2026-03-22-frontend-replacement-design.md deleted file mode 100644 index 65c294a..0000000 --- a/docs/superpowers/specs/2026-03-22-frontend-replacement-design.md +++ /dev/null @@ -1,74 +0,0 @@ -# Frontend Replacement: search-zen-50 Integration - -## Status -Approved - -## Overview - -Replace the current Go template-based frontend (HTMX + Go templates) with the search-zen-50 React SPA. The React app is built statically and embedded into the Go binary, serving as a single binary deployment. - -## Architecture - -- **Build**: React/Vite app builds to `dist/` directory -- **Embed**: Go's `//go:embed` embeds the dist folder into the binary -- **Serve**: Go HTTP server serves static files and handles API routes -- **SPA routing**: Non-API routes serve `index.html` for React Router - -## Changes - -### Go Side - -1. **Create `internal/spa/spa.go`** - - Embeds the React build (`dist/`) using `//go:embed` - - Serves static files (JS, CSS, images) - - Handles SPA fallback: serves `index.html` for all non-API routes - - Provides `SPAHandler` that wraps API routes - -2. **Modify `cmd/kafka/main.go`** - - Import the embedded SPA files - - Route `/`, `/preferences`, and unknown routes to SPA handler - - Keep existing API routes: `/search`, `/autocompleter`, `/healthz`, `/opensearch.xml` - -### React Side - -1. **Modify `use-search.ts`** - - Replace mock data with real API call: `fetch("/search?format=json&q=${encodeURIComponent(query)}")` - - Map response to existing `SearXNGResponse` type (already matches) - -2. **Add autocomplete** (optional enhancement) - - Call `/autocompleter?q=${encodeURIComponent(query)}` - - Display suggestions while typing - -3. **Keep unchanged** - - All UI components - - Preferences page (localStorage-based) - - Routing (React Router) - -## Data Flow - -``` -Browser → GET / → Go serves embedded index.html -Browser → GET /search?format=json&q=... → Go search handler → JSON -Browser → React renders results via use-search hook -``` - -## API Compatibility - -The existing kafka API (`/search?format=json`) already matches the expected `SearXNGResponse` interface in the React code: -- `query: string` -- `number_of_results: number` -- `results: SearchResult[]` -- `suggestions: string[]` -- `unresponsive_engines: string[][]` - -## File Changes - -- **New**: `internal/spa/spa.go` -- **Modified**: `cmd/kafka/main.go` (wire SPA handler) -- **Modified**: `src/hooks/use-search.ts` (use real API) -- **Build step**: `npm run build` or `bun run build` in search-zen-50 - -## Dependencies - -- React app uses `@tanstack/react-query` for API calls (already in package.json) -- No new Go dependencies needed diff --git a/frontend/.gitignore b/frontend/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/frontend/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index a125fd6..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Welcome to your Lovable project - -TODO: Document your project here diff --git a/frontend/components.json b/frontend/components.json deleted file mode 100644 index 62e1011..0000000 --- a/frontend/components.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "default", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "tailwind.config.ts", - "css": "src/index.css", - "baseColor": "slate", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - } -} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js deleted file mode 100644 index 40f72cc..0000000 --- a/frontend/eslint.config.js +++ /dev/null @@ -1,26 +0,0 @@ -import js from "@eslint/js"; -import globals from "globals"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactRefresh from "eslint-plugin-react-refresh"; -import tseslint from "typescript-eslint"; - -export default tseslint.config( - { ignores: ["dist"] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ["**/*.{ts,tsx}"], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - "react-hooks": reactHooks, - "react-refresh": reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], - "@typescript-eslint/no-unused-vars": "off", - }, - }, -); diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index c1ff5ee..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - kafka — Private Meta-Search - - - - - - - - - - - - - - - - - - -
- - - diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index e90cada..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "name": "vite_react_shadcn_ts", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "build:dev": "vite build --mode development", - "lint": "eslint .", - "preview": "vite preview", - "test": "vitest run", - "test:watch": "vitest" - }, - "dependencies": { - "@hookform/resolvers": "^3.10.0", - "@radix-ui/react-accordion": "^1.2.11", - "@radix-ui/react-alert-dialog": "^1.1.14", - "@radix-ui/react-aspect-ratio": "^1.1.7", - "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-checkbox": "^1.3.2", - "@radix-ui/react-collapsible": "^1.1.11", - "@radix-ui/react-context-menu": "^2.2.15", - "@radix-ui/react-dialog": "^1.1.14", - "@radix-ui/react-dropdown-menu": "^2.1.15", - "@radix-ui/react-hover-card": "^1.1.14", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-menubar": "^1.1.15", - "@radix-ui/react-navigation-menu": "^1.2.13", - "@radix-ui/react-popover": "^1.1.14", - "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-radio-group": "^1.3.7", - "@radix-ui/react-scroll-area": "^1.2.9", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slider": "^1.3.5", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.5", - "@radix-ui/react-tabs": "^1.1.12", - "@radix-ui/react-toast": "^1.2.14", - "@radix-ui/react-toggle": "^1.1.9", - "@radix-ui/react-toggle-group": "^1.1.10", - "@radix-ui/react-tooltip": "^1.2.7", - "@tanstack/react-query": "^5.83.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "date-fns": "^3.6.0", - "embla-carousel-react": "^8.6.0", - "input-otp": "^1.4.2", - "lucide-react": "^0.462.0", - "next-themes": "^0.3.0", - "react": "^18.3.1", - "react-day-picker": "^8.10.1", - "react-dom": "^18.3.1", - "react-hook-form": "^7.61.1", - "react-resizable-panels": "^2.1.9", - "react-router-dom": "^6.30.1", - "recharts": "^2.15.4", - "sonner": "^1.7.4", - "tailwind-merge": "^2.6.0", - "tailwindcss-animate": "^1.0.7", - "vaul": "^0.9.9", - "zod": "^3.25.76" - }, - "devDependencies": { - "@eslint/js": "^9.32.0", - "@playwright/test": "^1.57.0", - "@tailwindcss/typography": "^0.5.16", - "@testing-library/jest-dom": "^6.6.0", - "@testing-library/react": "^16.0.0", - "@types/node": "^22.16.5", - "@types/react": "^18.3.23", - "@types/react-dom": "^18.3.7", - "@vitejs/plugin-react-swc": "^3.11.0", - "autoprefixer": "^10.4.21", - "eslint": "^9.32.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^15.15.0", - "jsdom": "^20.0.3", - "lovable-tagger": "^1.1.13", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.17", - "typescript": "^5.8.3", - "typescript-eslint": "^8.38.0", - "vite": "^5.4.19", - "vitest": "^3.2.4" - } -} diff --git a/frontend/playwright-fixture.ts b/frontend/playwright-fixture.ts deleted file mode 100644 index 7d471c1..0000000 --- a/frontend/playwright-fixture.ts +++ /dev/null @@ -1,3 +0,0 @@ -// Re-export the base fixture from the package -// Override or extend test/expect here if needed -export { test, expect } from "lovable-agent-playwright-config/fixture"; diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts deleted file mode 100644 index ec19e95..0000000 --- a/frontend/playwright.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createLovableConfig } from "lovable-agent-playwright-config/config"; - -export default createLovableConfig({ - // Add your custom playwright configuration overrides here - // Example: - // timeout: 60000, - // use: { - // baseURL: 'http://localhost:3000', - // }, -}); diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index 2aa7205..0000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100644 index 3c01d69..0000000 Binary files a/frontend/public/favicon.ico and /dev/null differ diff --git a/frontend/public/placeholder.svg b/frontend/public/placeholder.svg deleted file mode 100644 index ea950de..0000000 --- a/frontend/public/placeholder.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt deleted file mode 100644 index 6018e70..0000000 --- a/frontend/public/robots.txt +++ /dev/null @@ -1,14 +0,0 @@ -User-agent: Googlebot -Allow: / - -User-agent: Bingbot -Allow: / - -User-agent: Twitterbot -Allow: / - -User-agent: facebookexternalhit -Allow: / - -User-agent: * -Allow: / diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx deleted file mode 100644 index f1ed102..0000000 --- a/frontend/src/App.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; -import { Toaster as Sonner } from "@/components/ui/sonner"; -import { Toaster } from "@/components/ui/toaster"; -import { TooltipProvider } from "@/components/ui/tooltip"; -import { PreferencesProvider } from "@/contexts/PreferencesContext"; -import Index from "./pages/Index.tsx"; -import Preferences from "./pages/Preferences.tsx"; -import NotFound from "./pages/NotFound.tsx"; - -const queryClient = new QueryClient(); - -const App = () => ( - - - - - - - - } /> - } /> - } /> - - - - - -); - -export default App; diff --git a/frontend/src/components/CategoryTabs.tsx b/frontend/src/components/CategoryTabs.tsx deleted file mode 100644 index 1be1eda..0000000 --- a/frontend/src/components/CategoryTabs.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Globe, Code, Image, Newspaper } from "lucide-react"; -import { CATEGORIES, type Category } from "@/lib/mock-data"; - -const CATEGORY_META: Record = { - general: { label: "General", icon: Globe }, - it: { label: "IT", icon: Code }, - images: { label: "Images", icon: Image }, - news: { label: "News", icon: Newspaper }, -}; - -interface CategoryTabsProps { - active: Category; - onChange: (c: Category) => void; -} - -export function CategoryTabs({ active, onChange }: CategoryTabsProps) { - return ( -
- {CATEGORIES.map((cat) => { - const { label, icon: Icon } = CATEGORY_META[cat]; - const isActive = cat === active; - return ( - - ); - })} -
- ); -} diff --git a/frontend/src/components/NavLink.tsx b/frontend/src/components/NavLink.tsx deleted file mode 100644 index a561a95..0000000 --- a/frontend/src/components/NavLink.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { NavLink as RouterNavLink, NavLinkProps } from "react-router-dom"; -import { forwardRef } from "react"; -import { cn } from "@/lib/utils"; - -interface NavLinkCompatProps extends Omit { - className?: string; - activeClassName?: string; - pendingClassName?: string; -} - -const NavLink = forwardRef( - ({ className, activeClassName, pendingClassName, to, ...props }, ref) => { - return ( - - cn(className, isActive && activeClassName, isPending && pendingClassName) - } - {...props} - /> - ); - }, -); - -NavLink.displayName = "NavLink"; - -export { NavLink }; diff --git a/frontend/src/components/ResultCard.tsx b/frontend/src/components/ResultCard.tsx deleted file mode 100644 index 62209eb..0000000 --- a/frontend/src/components/ResultCard.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { SearchResult } from "@/lib/mock-data"; - -interface ResultCardProps { - result: SearchResult; -} - -export function ResultCard({ result }: ResultCardProps) { - const domain = result.parsed_url[1]; - const faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`; - - return ( - -
- - {result.pretty_url} - {result.engines.length > 1 && ( - - {result.engines.length} engines - - )} -
-

- {result.title} -

-

- {result.content} -

- {result.publishedDate && ( - - {new Date(result.publishedDate).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric" })} - - )} -
- ); -} diff --git a/frontend/src/components/ResultSkeleton.tsx b/frontend/src/components/ResultSkeleton.tsx deleted file mode 100644 index 0119f4c..0000000 --- a/frontend/src/components/ResultSkeleton.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export function ResultSkeleton() { - return ( -
- {Array.from({ length: 5 }).map((_, i) => ( -
-
-
-
-
-
-
-
-
-
-
- ))} -
- ); -} diff --git a/frontend/src/components/SearchInput.tsx b/frontend/src/components/SearchInput.tsx deleted file mode 100644 index 9d1cfe5..0000000 --- a/frontend/src/components/SearchInput.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Search } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { FormEvent, useRef, useEffect } from "react"; - -interface SearchInputProps { - query: string; - onQueryChange: (q: string) => void; - onSearch: (q: string) => void; - compact?: boolean; - autoFocus?: boolean; -} - -export function SearchInput({ query, onQueryChange, onSearch, compact, autoFocus }: SearchInputProps) { - const inputRef = useRef(null); - - useEffect(() => { - if (autoFocus) inputRef.current?.focus(); - }, [autoFocus]); - - const handleSubmit = (e: FormEvent) => { - e.preventDefault(); - onSearch(query); - }; - - return ( -
-
- - onQueryChange(e.target.value)} - placeholder="Search the web privately..." - className={`pl-10 pr-4 border-input bg-background focus-visible:ring-ring ${compact ? "h-9 text-sm" : "h-12 text-base"}`} - /> -
- -
- ); -} diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx deleted file mode 100644 index 1e7878c..0000000 --- a/frontend/src/components/ui/accordion.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from "react"; -import * as AccordionPrimitive from "@radix-ui/react-accordion"; -import { ChevronDown } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const Accordion = AccordionPrimitive.Root; - -const AccordionItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AccordionItem.displayName = "AccordionItem"; - -const AccordionTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180", - className, - )} - {...props} - > - {children} - - - -)); -AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; - -const AccordionContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)); - -AccordionContent.displayName = AccordionPrimitive.Content.displayName; - -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/frontend/src/components/ui/alert-dialog.tsx b/frontend/src/components/ui/alert-dialog.tsx deleted file mode 100644 index 6dfbfb4..0000000 --- a/frontend/src/components/ui/alert-dialog.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from "react"; -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; - -import { cn } from "@/lib/utils"; -import { buttonVariants } from "@/components/ui/button"; - -const AlertDialog = AlertDialogPrimitive.Root; - -const AlertDialogTrigger = AlertDialogPrimitive.Trigger; - -const AlertDialogPortal = AlertDialogPrimitive.Portal; - -const AlertDialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; - -const AlertDialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - - - - -)); -AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; - -const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -AlertDialogHeader.displayName = "AlertDialogHeader"; - -const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -AlertDialogFooter.displayName = "AlertDialogFooter"; - -const AlertDialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; - -const AlertDialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; - -const AlertDialogAction = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; - -const AlertDialogCancel = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; - -export { - AlertDialog, - AlertDialogPortal, - AlertDialogOverlay, - AlertDialogTrigger, - AlertDialogContent, - AlertDialogHeader, - AlertDialogFooter, - AlertDialogTitle, - AlertDialogDescription, - AlertDialogAction, - AlertDialogCancel, -}; diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx deleted file mode 100644 index 2efc3c8..0000000 --- a/frontend/src/components/ui/alert.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const alertVariants = cva( - "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", - { - variants: { - variant: { - default: "bg-background text-foreground", - destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -const Alert = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes & VariantProps ->(({ className, variant, ...props }, ref) => ( -
-)); -Alert.displayName = "Alert"; - -const AlertTitle = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -AlertTitle.displayName = "AlertTitle"; - -const AlertDescription = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); -AlertDescription.displayName = "AlertDescription"; - -export { Alert, AlertTitle, AlertDescription }; diff --git a/frontend/src/components/ui/aspect-ratio.tsx b/frontend/src/components/ui/aspect-ratio.tsx deleted file mode 100644 index c9e6f4b..0000000 --- a/frontend/src/components/ui/aspect-ratio.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; - -const AspectRatio = AspectRatioPrimitive.Root; - -export { AspectRatio }; diff --git a/frontend/src/components/ui/avatar.tsx b/frontend/src/components/ui/avatar.tsx deleted file mode 100644 index 68d21bb..0000000 --- a/frontend/src/components/ui/avatar.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as React from "react"; -import * as AvatarPrimitive from "@radix-ui/react-avatar"; - -import { cn } from "@/lib/utils"; - -const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Avatar.displayName = AvatarPrimitive.Root.displayName; - -const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarImage.displayName = AvatarPrimitive.Image.displayName; - -const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; - -export { Avatar, AvatarImage, AvatarFallback }; diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx deleted file mode 100644 index 0853c44..0000000 --- a/frontend/src/components/ui/badge.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const badgeVariants = cva( - "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", - secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -export interface BadgeProps extends React.HTMLAttributes, VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return
; -} - -export { Badge, badgeVariants }; diff --git a/frontend/src/components/ui/breadcrumb.tsx b/frontend/src/components/ui/breadcrumb.tsx deleted file mode 100644 index ca91ff5..0000000 --- a/frontend/src/components/ui/breadcrumb.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { ChevronRight, MoreHorizontal } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const Breadcrumb = React.forwardRef< - HTMLElement, - React.ComponentPropsWithoutRef<"nav"> & { - separator?: React.ReactNode; - } ->(({ ...props }, ref) =>