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 + + Product 1 +
+

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 + + Product 1 +
+

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"