diff --git a/apps/web/app/search/page.tsx b/apps/web/app/search/page.tsx
new file mode 100644
index 00000000..01601748
--- /dev/null
+++ b/apps/web/app/search/page.tsx
@@ -0,0 +1,5 @@
+import { SearchView } from "views/Search/SearchView"
+
+export default function SearchPage() {
+ return
+}
diff --git "a/apps/web/components/\027\027\027search-view.tsx" "b/apps/web/components/\027\027\027search-view.tsx"
new file mode 100644
index 00000000..73756d0b
--- /dev/null
+++ "b/apps/web/components/\027\027\027search-view.tsx"
@@ -0,0 +1,159 @@
+/**
+ * This code was generated by v0 by Vercel.
+ * @see https://v0.dev/t/W1VkExg7Hvk
+ */
+import { AccordionTrigger, AccordionContent, AccordionItem, Accordion } from "components/ui/Accordion"
+import { Checkbox } from "components/ui/Checkbox"
+import { Label } from "components/ui/Label"
+import { Button } from "components/ui/Button"
+import { DropdownMenuTrigger, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, DropdownMenuContent, DropdownMenu } from "components/ui/DropdownMenu"
+import Link from "next/link"
+
+export function SearchView() {
+ return (
+
+
+
+
Filters
+
+
+ Category
+
+
+
+
+
+
+
+
+
+ Price Range
+
+
+
+
+
+
+
+
+
+
+ Rating
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Products
+
+
+
+
+
+ Sort by
+
+ Price: Low to High
+ Price: High to Low
+ Popularity
+ Newest
+
+
+
+
+
+
+
View
+
+
+
+
Stylish Sunglasses
+
UV protection
+
$29.99
+
+
+
+
+
+
+
+ )
+}
+
+
+function ArrowUpDownIcon(props) {
+ return (
+
+ )
+}
diff --git a/apps/web/components/ui/Accordion.tsx b/apps/web/components/ui/Accordion.tsx
new file mode 100644
index 00000000..2dbcba08
--- /dev/null
+++ b/apps/web/components/ui/Accordion.tsx
@@ -0,0 +1,53 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+import { cn } from "utils/cn"
+
+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/apps/web/components/ui/Button.tsx b/apps/web/components/ui/Button.tsx
new file mode 100644
index 00000000..5ef97fb9
--- /dev/null
+++ b/apps/web/components/ui/Button.tsx
@@ -0,0 +1,46 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+import { cn } from "utils/cn"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center 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",
+ {
+ 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/apps/web/components/ui/Checkbox.tsx b/apps/web/components/ui/Checkbox.tsx
new file mode 100644
index 00000000..e1fbcab0
--- /dev/null
+++ b/apps/web/components/ui/Checkbox.tsx
@@ -0,0 +1,27 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+import { cn } from "utils/cn"
+
+const Checkbox = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/apps/web/components/ui/DropdownMenu.tsx b/apps/web/components/ui/DropdownMenu.tsx
new file mode 100644
index 00000000..9949d092
--- /dev/null
+++ b/apps/web/components/ui/DropdownMenu.tsx
@@ -0,0 +1,180 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+import { cn } from "utils/cn"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
+ return
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/apps/web/components/ui/Label.tsx b/apps/web/components/ui/Label.tsx
new file mode 100644
index 00000000..b9d60cc4
--- /dev/null
+++ b/apps/web/components/ui/Label.tsx
@@ -0,0 +1,18 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+import { cn } from "utils/cn"
+
+const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70")
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index db622df1..249179f3 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -15,6 +15,10 @@ const config = withPlugins([[withBundleAnalyzer({ enabled: env.ANALYZE })]], {
{ source: "/api/healthz", destination: "/api/health" },
{ source: "/health", destination: "/api/health" },
{ source: "/ping", destination: "/api/health" },
+ {
+ source: "/search/:second",
+ destination: "/search?second=:second",
+ },
]
},
})
diff --git a/apps/web/package.json b/apps/web/package.json
index a70e1bb9..1ab430dd 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -36,6 +36,7 @@
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "2.0.0",
"@radix-ui/react-slider": "^1.1.2",
+ "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle-group": "^1.0.4",
@@ -50,6 +51,7 @@
"@vercel/otel": "^0.3.0",
"class-variance-authority": "^0.7.0",
"lodash": "^4.17.21",
+ "lucide-react": "^0.312.0",
"next-compose-plugins": "^2.2.1",
"playwright": "^1.40.1",
"react": "^18.2.0",
diff --git a/apps/web/views/Search/SearchView.tsx b/apps/web/views/Search/SearchView.tsx
new file mode 100644
index 00000000..d6dd8f44
--- /dev/null
+++ b/apps/web/views/Search/SearchView.tsx
@@ -0,0 +1,157 @@
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "components/ui/Accordion"
+import { Button } from "components/ui/Button"
+import { Checkbox } from "components/ui/Checkbox"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "components/ui/DropdownMenu"
+import { Label } from "components/ui/Label"
+import Link from "next/link"
+
+export function SearchView() {
+ return (
+
+
+
+
Filters
+
+
+ Category
+
+
+
+
+
+
+
+
+
+ Price Range
+
+
+
+
+
+
+
+
+
+
+ Rating
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Products
+
+
+
+
+
+ Sort by
+
+ Price: Low to High
+ Price: High to Low
+ Popularity
+ Newest
+
+
+
+
+
+
+
View
+
+
+
+
Stylish Sunglasses
+
UV protection
+
$29.99
+
+
+
+
+
+
+
+ )
+}
+
+function ArrowUpDownIcon(props) {
+ return (
+
+ )
+}
diff --git a/yarn.lock b/yarn.lock
index ebafdcb7..2fbf821a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3813,7 +3813,7 @@
"@radix-ui/react-use-previous" "1.0.1"
"@radix-ui/react-use-size" "1.0.1"
-"@radix-ui/react-slot@1.0.2":
+"@radix-ui/react-slot@1.0.2", "@radix-ui/react-slot@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==
@@ -12845,6 +12845,11 @@ lru-cache@^7.7.1:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
+lucide-react@^0.312.0:
+ version "0.312.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.312.0.tgz#2dee3f46068eb01aa060ff7cb60d430c107decb5"
+ integrity sha512-3UZsqyswRXjW4t+nw+InICewSimjPKHuSxiFYqTshv9xkK3tPPntXk/lvXc9pKlXIxm3v9WKyoxcrB6YHhP+dg==
+
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"