From d6624b1ff235067b74f340b1031926a6d73239a3 Mon Sep 17 00:00:00 2001 From: Wallace Osmar Date: Sun, 3 Aug 2025 18:04:15 -0300 Subject: [PATCH] feat: implement NotFound page with search functionality and UI components feat: add Card, Input, and Label components for UI consistency refactor: update Button component to include cursor pointer style --- apps/web/src/app/not-found.tsx | 100 ++++++++++++++++++++++++++ packages/ui/src/components/button.tsx | 2 +- packages/ui/src/components/card.tsx | 92 ++++++++++++++++++++++++ packages/ui/src/components/input.tsx | 21 ++++++ packages/ui/src/components/label.tsx | 24 +++++++ 5 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/app/not-found.tsx create mode 100644 packages/ui/src/components/card.tsx create mode 100644 packages/ui/src/components/input.tsx create mode 100644 packages/ui/src/components/label.tsx diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx new file mode 100644 index 0000000..45ee1e7 --- /dev/null +++ b/apps/web/src/app/not-found.tsx @@ -0,0 +1,100 @@ +"use client" + +import { Button } from "@workspace/ui/components/button"; +import { Card, CardContent } from "@workspace/ui/components/card"; +import { Input } from "@workspace/ui/components/input"; +import { ArrowLeft, BookOpen, Home, Search } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useState } from "react"; + +export default function NotFound() { + const [searchQuery, setSearchQuery] = useState("") + const router = useRouter() + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault() + if (searchQuery.trim()) { + router.push(`/search?q=${encodeURIComponent(searchQuery.trim())}`) + } + } + + return ( +
+
+
+ {/* 404 Illustration */} +
+
+
404
+
+
+ +
+
+
+ +

Page Not Found

+

+ Oops! The page you{"'"}re looking for seems to have wandered off into another dimension. + Don{"'"}t worry, we{"'"}ll help you find your way back to amazing stories! +

+
+ {/* Search Section */} + + +
+

Search for Stories

+

Looking for a specific novel? Try searching for it below.

+
+ +
+
+ + setSearchQuery(e.target.value)} + className="pl-10 pr-4 h-12 text-base" + /> + +
+
+
+
+ {/* Action Buttons */} +
+ + + + + +
+ {/* Help Text */} +
+

+ Still can{"'"}t find what you{"'"}re looking for?{" "} + + Contact our support team + {" "} + for assistance. +

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx index 9575a7e..6129251 100644 --- a/packages/ui/src/components/button.tsx +++ b/packages/ui/src/components/button.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@workspace/ui/lib/utils" const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer", { variants: { variant: { diff --git a/packages/ui/src/components/card.tsx b/packages/ui/src/components/card.tsx new file mode 100644 index 0000000..21a80ef --- /dev/null +++ b/packages/ui/src/components/card.tsx @@ -0,0 +1,92 @@ +import * as React from "react" + +import { cn } from "@workspace/ui/lib/utils" + +function Card({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/packages/ui/src/components/input.tsx b/packages/ui/src/components/input.tsx new file mode 100644 index 0000000..cc414ad --- /dev/null +++ b/packages/ui/src/components/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@workspace/ui/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/packages/ui/src/components/label.tsx b/packages/ui/src/components/label.tsx new file mode 100644 index 0000000..4aeafb5 --- /dev/null +++ b/packages/ui/src/components/label.tsx @@ -0,0 +1,24 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" + +import { cn } from "@workspace/ui/lib/utils" + +function Label({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Label }