├── .prettierrc ├── bun.lockb ├── src ├── app │ ├── favicon.ico │ ├── api │ │ └── [[...route]] │ │ │ ├── route.ts │ │ │ └── global-search.ts │ ├── layout.tsx │ ├── page.tsx │ ├── demo-redirect-page │ │ └── page.tsx │ ├── globals.css │ └── use-cases │ │ ├── main-menu │ │ └── page.tsx │ │ └── side-bar │ │ └── page.tsx ├── lib │ ├── utils.ts │ └── hono.ts ├── components │ ├── ui │ │ ├── aspect-ratio.tsx │ │ ├── skeleton.tsx │ │ ├── collapsible.tsx │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── separator.tsx │ │ ├── progress.tsx │ │ ├── toaster.tsx │ │ ├── input.tsx │ │ ├── sonner.tsx │ │ ├── checkbox.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── badge.tsx │ │ ├── tooltip.tsx │ │ ├── hover-card.tsx │ │ ├── popover.tsx │ │ ├── avatar.tsx │ │ ├── toggle.tsx │ │ ├── radio-group.tsx │ │ ├── alert.tsx │ │ ├── scroll-area.tsx │ │ ├── resizable.tsx │ │ ├── toggle-group.tsx │ │ ├── button.tsx │ │ ├── tabs.tsx │ │ ├── card.tsx │ │ ├── accordion.tsx │ │ ├── input-otp.tsx │ │ ├── calendar.tsx │ │ ├── breadcrumb.tsx │ │ ├── pagination.tsx │ │ ├── table.tsx │ │ ├── drawer.tsx │ │ ├── dialog.tsx │ │ ├── sheet.tsx │ │ ├── form.tsx │ │ ├── alert-dialog.tsx │ │ ├── toast.tsx │ │ ├── navigation-menu.tsx │ │ ├── command.tsx │ │ ├── select.tsx │ │ ├── carousel.tsx │ │ ├── context-menu.tsx │ │ ├── dropdown-menu.tsx │ │ └── menubar.tsx │ ├── use-case-link.tsx │ └── about-info │ │ └── about-info.tsx ├── constants │ └── common.ts ├── database │ ├── drizzle.ts │ └── schema.ts ├── types │ ├── icon-mapping.tsx │ └── global-search.ts ├── features │ └── global-search │ │ ├── components │ │ ├── loading-spinner.tsx │ │ ├── recent-searches.tsx │ │ ├── trending-searches.tsx │ │ ├── highlight-text.tsx │ │ ├── default-search-view.tsx │ │ ├── search-results.tsx │ │ ├── no-results-placeholder.tsx │ │ ├── search-wrapper.tsx │ │ ├── search-result-item.tsx │ │ └── search-input.tsx │ │ ├── store │ │ └── global-search-store.ts │ │ ├── hooks │ │ └── use-global-search.ts │ │ ├── queries │ │ └── use-search-query.ts │ │ └── index.tsx ├── providers │ └── query.provider.tsx └── hooks │ └── use-toast.ts ├── .env-sample ├── postcss.config.mjs ├── next.config.mjs ├── drizzle ├── meta │ ├── _journal.json │ └── 0000_snapshot.json └── 0000_familiar_starhawk.sql ├── .eslintrc.json ├── drizzle.config.ts ├── components.json ├── .gitignore ├── tsconfig.json ├── license.md ├── tailwind.config.ts ├── package.json └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"] 3 | } -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaraBharat/full-stack-global-search-demo/HEAD/bun.lockb -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaraBharat/full-stack-global-search-demo/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /.env-sample: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_APP_URL=http://localhost:3000/ 2 | DATABASE_URL='postgresql://xxxxxxx-xxxxxx:xxxxxxx-xxxxxx@xxxxxxx-xxxxxx.db.neon.tech/xxxxxxx-xxxxxx?sslmode=require' -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [{ hostname: "www.bharatkara.com" }], 5 | }, 6 | }; 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /src/constants/common.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Common Constants 3 | */ 4 | 5 | /** 6 | * Page Sizes 7 | */ 8 | export const PAGE_SIZE = 10; 9 | 10 | /** 11 | * Trading Page Size 12 | */ 13 | export const TRADING_PAGE_SIZE = 5; 14 | -------------------------------------------------------------------------------- /drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "postgresql", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "7", 8 | "when": 1728561620029, 9 | "tag": "0000_familiar_starhawk", 10 | "breakpoints": true 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"], 3 | "rules": { 4 | "no-empty-interface": 0, 5 | "@typescript-eslint/no-empty-interface": 0, 6 | "no-unused-vars": 0, 7 | "@typescript-eslint/no-unused-vars": 0, 8 | "no-empty-object-type": 0, 9 | "@typescript-eslint/no-empty-object-type": 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | import { defineConfig } from "drizzle-kit"; 3 | 4 | config({ path: ".env.local" }); 5 | 6 | export default defineConfig({ 7 | schema: "./src/database/schema.ts", 8 | dialect: "postgresql", 9 | dbCredentials: { 10 | url: process.env.DATABASE_URL!, 11 | }, 12 | verbose: true, 13 | strict: true, 14 | }); -------------------------------------------------------------------------------- /src/database/drizzle.ts: -------------------------------------------------------------------------------- 1 | import { neon } from "@neondatabase/serverless"; 2 | import { drizzle } from "drizzle-orm/neon-http"; 3 | 4 | /** 5 | * Database Connection Setup 6 | */ 7 | 8 | // Initialize the Neon database connection 9 | export const sql = neon(process.env.DATABASE_URL!); 10 | 11 | // Create a Drizzle ORM instance 12 | export const db = drizzle(sql); 13 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /drizzle/0000_familiar_starhawk.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "global_search_index" ( 2 | "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, 3 | "type" varchar(50) NOT NULL, 4 | "name" varchar(255) NOT NULL, 5 | "description" text, 6 | "tags" text[], 7 | "date" timestamp NOT NULL, 8 | "status" varchar(50) DEFAULT 'active' NOT NULL, 9 | "breadcrumbs" text[], 10 | "url" varchar(255), 11 | "trending" boolean DEFAULT false, 12 | "trending_rank" integer 13 | ); 14 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "stone", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /src/lib/hono.ts: -------------------------------------------------------------------------------- 1 | import { AppType } from "@/app/api/[[...route]]/route"; 2 | import { hc } from "hono/client"; 3 | 4 | /** 5 | * Hono client configuration 6 | * This client is used for making API requests to the application's backend. 7 | */ 8 | 9 | /** 10 | * Create a typed Hono client instance 11 | * @type {ReturnType>} 12 | */ 13 | export const client = hc(process.env.NEXT_PUBLIC_APP_URL!); 14 | 15 | // Ensure the environment variable is set 16 | if (!process.env.NEXT_PUBLIC_APP_URL) { 17 | console.warn("API URL is not set. API requests may fail."); 18 | } 19 | -------------------------------------------------------------------------------- /src/types/icon-mapping.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BarChart, 3 | Briefcase, 4 | Calendar, 5 | File, 6 | Globe, 7 | LucideIcon, 8 | Users, 9 | } from "lucide-react"; 10 | import { SearchItemType } from "./global-search"; 11 | 12 | /** 13 | * Mapping of search item types to their corresponding Lucide icons. 14 | * This object is used to display appropriate icons for different search result types. 15 | */ 16 | export const GlobalSearchIconMap: Record = { 17 | all: Globe, 18 | file: File, 19 | team: Users, 20 | calendar: Calendar, 21 | analytics: BarChart, 22 | project: Briefcase, 23 | global: Globe, 24 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /src/features/global-search/components/loading-spinner.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from "lucide-react"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | interface LoadingSpinnerProps { 5 | className?: string; 6 | } 7 | 8 | /** 9 | * LoadingSpinner component 10 | * Displays a centered spinning loader icon 11 | * @param {LoadingSpinnerProps} props - Component props 12 | * @returns {JSX.Element} Rendered LoadingSpinner component 13 | */ 14 | export const LoadingSpinner = ({ className }: LoadingSpinnerProps): JSX.Element => ( 15 |
16 | 21 |
22 | ); -------------------------------------------------------------------------------- /src/features/global-search/components/recent-searches.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SearchResults } from './search-results'; 3 | import { SearchItem } from '@/types/global-search'; 4 | 5 | interface RecentSearchesProps { 6 | results: SearchItem[]; 7 | } 8 | 9 | /** 10 | * RecentSearches component 11 | * Displays a list of recent search results 12 | * @param {RecentSearchesProps} props - Component props 13 | * @returns {JSX.Element} Rendered RecentSearches component 14 | */ 15 | export const RecentSearches: React.FC = ({ results }) => ( 16 |
17 |

18 | Recent Searches 19 |

20 | 21 |
22 | ); -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /src/features/global-search/components/trending-searches.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SearchItem } from '@/types/global-search'; 3 | import { SearchResults } from './search-results'; 4 | 5 | /** 6 | * TrendingSearches component displays a list of top trending searches. 7 | * 8 | * @param {Object} props - Component props 9 | * @param {SearchItem[]} props.results - Array of trending search items 10 | * @returns {React.ReactElement} Rendered component 11 | */ 12 | export const TrendingSearches: React.FC<{ results: SearchItem[] }> = ({ results }) => ( 13 |
14 | 20 | 21 |
22 | ); -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |