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 (
-
- );
-}
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) => );
-Breadcrumb.displayName = "Breadcrumb";
-
-const BreadcrumbList = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-BreadcrumbList.displayName = "BreadcrumbList";
-
-const BreadcrumbItem = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-BreadcrumbItem.displayName = "BreadcrumbItem";
-
-const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<"a"> & {
- asChild?: boolean;
- }
->(({ asChild, className, ...props }, ref) => {
- const Comp = asChild ? Slot : "a";
-
- return ;
-});
-BreadcrumbLink.displayName = "BreadcrumbLink";
-
-const BreadcrumbPage = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-BreadcrumbPage.displayName = "BreadcrumbPage";
-
-const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
- svg]:size-3.5", className)} {...props}>
- {children ?? }
-
-);
-BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
-
-const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
-
-
- More
-
-);
-BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
-
-export {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbPage,
- BreadcrumbSeparator,
- BreadcrumbEllipsis,
-};
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
deleted file mode 100644
index cdedd4f..0000000
--- a/frontend/src/components/ui/button.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-10 px-4 py-2",
- sm: "h-9 rounded-md px-3",
- lg: "h-11 rounded-md px-8",
- icon: "h-10 w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-export interface ButtonProps
- extends React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean;
-}
-
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
- return ;
- },
-);
-Button.displayName = "Button";
-
-export { Button, buttonVariants };
diff --git a/frontend/src/components/ui/calendar.tsx b/frontend/src/components/ui/calendar.tsx
deleted file mode 100644
index 900a69e..0000000
--- a/frontend/src/components/ui/calendar.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as React from "react";
-import { ChevronLeft, ChevronRight } from "lucide-react";
-import { DayPicker } from "react-day-picker";
-
-import { cn } from "@/lib/utils";
-import { buttonVariants } from "@/components/ui/button";
-
-export type CalendarProps = React.ComponentProps;
-
-function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
- return (
- ,
- IconRight: ({ ..._props }) => ,
- }}
- {...props}
- />
- );
-}
-Calendar.displayName = "Calendar";
-
-export { Calendar };
diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx
deleted file mode 100644
index e282748..0000000
--- a/frontend/src/components/ui/card.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as React from "react";
-
-import { cn } from "@/lib/utils";
-
-const Card = React.forwardRef>(({ className, ...props }, ref) => (
-
-));
-Card.displayName = "Card";
-
-const CardHeader = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-CardHeader.displayName = "CardHeader";
-
-const CardTitle = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-CardTitle.displayName = "CardTitle";
-
-const CardDescription = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-CardDescription.displayName = "CardDescription";
-
-const CardContent = React.forwardRef>(
- ({ className, ...props }, ref) => ,
-);
-CardContent.displayName = "CardContent";
-
-const CardFooter = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- ),
-);
-CardFooter.displayName = "CardFooter";
-
-export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
diff --git a/frontend/src/components/ui/carousel.tsx b/frontend/src/components/ui/carousel.tsx
deleted file mode 100644
index 3aa0f31..0000000
--- a/frontend/src/components/ui/carousel.tsx
+++ /dev/null
@@ -1,224 +0,0 @@
-import * as React from "react";
-import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react";
-import { ArrowLeft, ArrowRight } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-import { Button } from "@/components/ui/button";
-
-type CarouselApi = UseEmblaCarouselType[1];
-type UseCarouselParameters = Parameters;
-type CarouselOptions = UseCarouselParameters[0];
-type CarouselPlugin = UseCarouselParameters[1];
-
-type CarouselProps = {
- opts?: CarouselOptions;
- plugins?: CarouselPlugin;
- orientation?: "horizontal" | "vertical";
- setApi?: (api: CarouselApi) => void;
-};
-
-type CarouselContextProps = {
- carouselRef: ReturnType[0];
- api: ReturnType[1];
- scrollPrev: () => void;
- scrollNext: () => void;
- canScrollPrev: boolean;
- canScrollNext: boolean;
-} & CarouselProps;
-
-const CarouselContext = React.createContext(null);
-
-function useCarousel() {
- const context = React.useContext(CarouselContext);
-
- if (!context) {
- throw new Error("useCarousel must be used within a ");
- }
-
- return context;
-}
-
-const Carousel = React.forwardRef & CarouselProps>(
- ({ orientation = "horizontal", opts, setApi, plugins, className, children, ...props }, ref) => {
- const [carouselRef, api] = useEmblaCarousel(
- {
- ...opts,
- axis: orientation === "horizontal" ? "x" : "y",
- },
- plugins,
- );
- const [canScrollPrev, setCanScrollPrev] = React.useState(false);
- const [canScrollNext, setCanScrollNext] = React.useState(false);
-
- const onSelect = React.useCallback((api: CarouselApi) => {
- if (!api) {
- return;
- }
-
- setCanScrollPrev(api.canScrollPrev());
- setCanScrollNext(api.canScrollNext());
- }, []);
-
- const scrollPrev = React.useCallback(() => {
- api?.scrollPrev();
- }, [api]);
-
- const scrollNext = React.useCallback(() => {
- api?.scrollNext();
- }, [api]);
-
- const handleKeyDown = React.useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === "ArrowLeft") {
- event.preventDefault();
- scrollPrev();
- } else if (event.key === "ArrowRight") {
- event.preventDefault();
- scrollNext();
- }
- },
- [scrollPrev, scrollNext],
- );
-
- React.useEffect(() => {
- if (!api || !setApi) {
- return;
- }
-
- setApi(api);
- }, [api, setApi]);
-
- React.useEffect(() => {
- if (!api) {
- return;
- }
-
- onSelect(api);
- api.on("reInit", onSelect);
- api.on("select", onSelect);
-
- return () => {
- api?.off("select", onSelect);
- };
- }, [api, onSelect]);
-
- return (
-
-
- {children}
-
-
- );
- },
-);
-Carousel.displayName = "Carousel";
-
-const CarouselContent = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { carouselRef, orientation } = useCarousel();
-
- return (
-
- );
- },
-);
-CarouselContent.displayName = "CarouselContent";
-
-const CarouselItem = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { orientation } = useCarousel();
-
- return (
-
- );
- },
-);
-CarouselItem.displayName = "CarouselItem";
-
-const CarouselPrevious = React.forwardRef>(
- ({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
-
- return (
-
- );
- },
-);
-CarouselPrevious.displayName = "CarouselPrevious";
-
-const CarouselNext = React.forwardRef>(
- ({ className, variant = "outline", size = "icon", ...props }, ref) => {
- const { orientation, scrollNext, canScrollNext } = useCarousel();
-
- return (
-
- );
- },
-);
-CarouselNext.displayName = "CarouselNext";
-
-export { type CarouselApi, Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext };
diff --git a/frontend/src/components/ui/chart.tsx b/frontend/src/components/ui/chart.tsx
deleted file mode 100644
index 08d40d9..0000000
--- a/frontend/src/components/ui/chart.tsx
+++ /dev/null
@@ -1,303 +0,0 @@
-import * as React from "react";
-import * as RechartsPrimitive from "recharts";
-
-import { cn } from "@/lib/utils";
-
-// Format: { THEME_NAME: CSS_SELECTOR }
-const THEMES = { light: "", dark: ".dark" } as const;
-
-export type ChartConfig = {
- [k in string]: {
- label?: React.ReactNode;
- icon?: React.ComponentType;
- } & ({ color?: string; theme?: never } | { color?: never; theme: Record });
-};
-
-type ChartContextProps = {
- config: ChartConfig;
-};
-
-const ChartContext = React.createContext(null);
-
-function useChart() {
- const context = React.useContext(ChartContext);
-
- if (!context) {
- throw new Error("useChart must be used within a ");
- }
-
- return context;
-}
-
-const ChartContainer = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> & {
- config: ChartConfig;
- children: React.ComponentProps["children"];
- }
->(({ id, className, children, config, ...props }, ref) => {
- const uniqueId = React.useId();
- const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
-
- return (
-
-
-
- {children}
-
-
- );
-});
-ChartContainer.displayName = "Chart";
-
-const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
- const colorConfig = Object.entries(config).filter(([_, config]) => config.theme || config.color);
-
- if (!colorConfig.length) {
- return null;
- }
-
- return (
-