diff --git a/app/(admin)/booking/page.tsx b/app/(admin)/booking/page.tsx new file mode 100644 index 0000000..3e40f07 --- /dev/null +++ b/app/(admin)/booking/page.tsx @@ -0,0 +1,18 @@ +"use client"; + +import SideNav from "@/components/side-nav"; + +export default function Booking() { + return ( +
+ {/* Side Navigation */} + + +
+
+
+
+
+ ); +} + diff --git a/app/(admin)/dashboard/page.tsx b/app/(admin)/dashboard/page.tsx new file mode 100644 index 0000000..5de5e31 --- /dev/null +++ b/app/(admin)/dashboard/page.tsx @@ -0,0 +1,20 @@ +"use client"; + +import SideNav from "@/components/side-nav"; + +export default function Dashboard() { + return ( +
+ {/* Side Navigation */} + + +
+ {/* Main Content */} +
+

Dashboard

+
+
+
+ ); +} + diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx new file mode 100644 index 0000000..db27d60 --- /dev/null +++ b/app/(admin)/layout.tsx @@ -0,0 +1,7 @@ +export default function AdminLayout({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ) +} \ No newline at end of file diff --git a/app/(admin)/login/page.tsx b/app/(admin)/login/page.tsx new file mode 100644 index 0000000..11f95cc --- /dev/null +++ b/app/(admin)/login/page.tsx @@ -0,0 +1,143 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Heart, Eye, EyeOff, X } from "lucide-react"; +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; + +export default function Login() { + const [showPassword, setShowPassword] = useState(false); + const [rememberMe, setRememberMe] = useState(false); + const router = useRouter(); + + return ( +
+ {/* Background Image */} +
+ Medical professionals + {/* Overlay for better readability */} +
+
+ + {/* Branding - Top Left */} +
+ + Attune Heart Therapy +
+ + + + {/* Centered White Card - Login Form */} +
+ {/* Close Button */} + + + {/* Heading */} +

+ Welcome back +

+ + {/* Sign Up Prompt */} +

+ New to Attune Heart Therapy?{" "} + + Sign up + +

+ + {/* Login Form */} +
e.preventDefault()}> + {/* Email Field */} +
+ + +
+ + {/* Password Field */} +
+ +
+ + +
+
+ + {/* Submit Button */} + + + {/* Remember Me & Forgot Password */} +
+ + + Forgot password? + +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/app/(admin)/notifications/page.tsx b/app/(admin)/notifications/page.tsx new file mode 100644 index 0000000..6ac1d97 --- /dev/null +++ b/app/(admin)/notifications/page.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { useState } from "react"; +import SideNav from "@/components/side-nav"; +import { Notifications, Notification } from "@/components/notifications"; + +export default function NotificationsPage() { + const [notifications, setNotifications] = useState([ + { + id: "1", + type: "appointment", + title: "New Appointment Request", + message: "Sarah Johnson requested an appointment for tomorrow at 2:00 PM", + time: "2 minutes ago", + read: false, + }, + { + id: "2", + type: "success", + title: "Appointment Confirmed", + message: "Your appointment with Michael Chen has been confirmed for today at 10:00 AM", + time: "1 hour ago", + read: false, + }, + { + id: "3", + type: "warning", + title: "Appointment Reminder", + message: "You have an appointment in 30 minutes with Emily Davis", + time: "3 hours ago", + read: false, + }, + { + id: "4", + type: "info", + title: "New Message", + message: "You received a new message from John Smith", + time: "5 hours ago", + read: true, + }, + { + id: "5", + type: "appointment", + title: "Appointment Cancelled", + message: "Robert Wilson cancelled his appointment scheduled for tomorrow", + time: "1 day ago", + read: true, + }, + ]); + + const handleMarkAsRead = (id: string) => { + setNotifications((prev) => + prev.map((n) => (n.id === id ? { ...n, read: true } : n)) + ); + }; + + const handleDismiss = (id: string) => { + setNotifications((prev) => prev.filter((n) => n.id !== id)); + }; + + const handleMarkAllAsRead = () => { + setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); + }; + + return ( +
+ {/* Side Navigation */} + + +
+
+ +
+
+
+ ); +} + diff --git a/app/globals.css b/app/globals.css index a2dc41e..4bc4c9d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,125 @@ @import "tailwindcss"; +@import "tw-animate-css"; -:root { - --background: #ffffff; - --foreground: #171717; -} +@custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --font-sans: var(--font-poppins); + --font-mono: var(--font-poppins); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + body.menu-open { + overflow: hidden; } } - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..affedce 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,20 +1,16 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Poppins } from "next/font/google"; import "./globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const poppins = Poppins({ + variable: "--font-poppins", subsets: ["latin"], + weight: ["300", "400", "500", "600", "700"], }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Attune Heart Therapy", + description: "Attune Heart Therapy", }; export default function RootLayout({ @@ -25,7 +21,7 @@ export default function RootLayout({ return ( {children} diff --git a/components.json b/components.json new file mode 100644 index 0000000..b7b9791 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/components/notifications.tsx b/components/notifications.tsx new file mode 100644 index 0000000..a4959e9 --- /dev/null +++ b/components/notifications.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Bell, + X, + CheckCircle, + AlertCircle, + Info, + Calendar, + Clock, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +export interface Notification { + id: string; + type: "success" | "warning" | "info" | "appointment"; + title: string; + message: string; + time: string; + read: boolean; +} + +interface NotificationsProps { + notifications: Notification[]; + onMarkAsRead?: (id: string) => void; + onDismiss?: (id: string) => void; + onMarkAllAsRead?: () => void; +} + +export function Notifications({ + notifications, + onMarkAsRead, + onDismiss, + onMarkAllAsRead, +}: NotificationsProps) { + const unreadCount = notifications.filter((n) => !n.read).length; + + const getIcon = (type: Notification["type"]) => { + switch (type) { + case "success": + return ; + case "warning": + return ; + case "info": + return ; + case "appointment": + return ; + } + }; + + const getBgColor = (type: Notification["type"]) => { + switch (type) { + case "success": + return "bg-[#4A90A4]/10 border-[#4A90A4]/30"; + case "warning": + return "bg-rose-100 border-rose-300"; + case "info": + return "bg-pink-50 border-pink-200"; + case "appointment": + return "bg-gradient-to-br from-rose-50 to-pink-50 border-rose-300"; + } + }; + + return ( +
+ {/* Header */} +
+
+ +

Notifications

+ {unreadCount > 0 && ( + + {unreadCount} + + )} +
+ {unreadCount > 0 && onMarkAllAsRead && ( + + )} +
+ + {/* Notifications List */} +
+ {notifications.length === 0 ? ( +
+ +

No notifications

+
+ ) : ( + notifications.map((notification) => ( +
+
+
{getIcon(notification.type)}
+
+
+
+

+ {notification.title} +

+

{notification.message}

+
+ + {notification.time} +
+
+
+ {!notification.read && onMarkAsRead && ( + + )} + {onDismiss && ( + + )} +
+
+
+
+
+ )) + )} +
+
+ ); +} + +// Notification Bell Component for Header +export function NotificationBell({ + count, + onClick, +}: { + count: number; + onClick: () => void; +}) { + return ( + + ); +} + diff --git a/components/side-nav.tsx b/components/side-nav.tsx new file mode 100644 index 0000000..1b8bf54 --- /dev/null +++ b/components/side-nav.tsx @@ -0,0 +1,156 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import { + LayoutGrid, + Calendar, + Settings, + LogOut, + Menu, + X, + Heart, +} from "lucide-react"; + +const navItems = [ + { label: "Dashboard", icon: LayoutGrid, href: "/dashboard" }, + { label: "Book Appointment", icon: Calendar, href: "/booking" }, +]; + +export default function SideNav() { + const [open, setOpen] = useState(false); + const pathname = usePathname(); + + const getActiveIndex = () => { + return navItems.findIndex((item) => pathname?.includes(item.href)) ?? -1; + }; + + // Handle body scroll when mobile menu is open + useEffect(() => { + if (open) { + document.body.classList.add("menu-open"); + } else { + document.body.classList.remove("menu-open"); + } + return () => { + document.body.classList.remove("menu-open"); + }; + }, [open]); + + return ( + <> + {/* Mobile Top Bar */} +
+
+
+ +
+ Attune Heart Therapy +
+ +
+ + {/* Mobile Drawer Overlay */} +
setOpen(false)} + /> + + {/* Side Navigation */} + + + ); +} + diff --git a/components/ui/button.tsx b/components/ui/button.tsx new file mode 100644 index 0000000..21409a0 --- /dev/null +++ b/components/ui/button.tsx @@ -0,0 +1,60 @@ +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 transition-all 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", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant, + size, + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/components/ui/input.tsx b/components/ui/input.tsx new file mode 100644 index 0000000..8916905 --- /dev/null +++ b/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/package.json b/package.json index 08b55f9..1dde493 100644 --- a/package.json +++ b/package.json @@ -8,22 +8,29 @@ "start": "next start", "lint": "eslint" }, - "engines": { + "engines": { "node": ">=20.9.0" }, "dependencies": { + "@radix-ui/react-slot": "^1.2.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.552.0", + "next": "16.0.1", "react": "19.2.0", "react-dom": "19.2.0", - "next": "16.0.1" + "tailwind-merge": "^3.3.1" }, "devDependencies": { - "typescript": "^5", + "@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", - "eslint-config-next": "16.0.1" - } + "eslint-config-next": "16.0.1", + "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", + "typescript": "^5" + }, + "packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64de4bb..119e817 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,18 @@ importers: .: dependencies: + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.2)(react@19.2.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.552.0 + version: 0.552.0(react@19.2.0) next: specifier: 16.0.1 version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -17,6 +29,9 @@ importers: react-dom: specifier: 19.2.0 version: 19.2.0(react@19.2.0) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -39,6 +54,9 @@ importers: tailwindcss: specifier: ^4 version: 4.1.16 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 typescript: specifier: ^5 version: 5.9.3 @@ -394,6 +412,24 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -785,9 +821,16 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1446,6 +1489,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.552.0: + resolution: {integrity: sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1781,6 +1829,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + tailwindcss@4.1.16: resolution: {integrity: sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==} @@ -1808,6 +1859,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2236,6 +2290,19 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + '@rtsao/scc@1.1.0': {} '@swc/helpers@0.5.15': @@ -2637,8 +2704,14 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + client-only@0.0.1: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3427,6 +3500,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.552.0(react@19.2.0): + dependencies: + react: 19.2.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3828,6 +3905,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + tailwind-merge@3.3.1: {} + tailwindcss@4.1.16: {} tapable@2.3.0: {} @@ -3854,6 +3933,8 @@ snapshots: tslib@2.8.1: {} + tw-animate-css@1.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/public/doctors.png b/public/doctors.png new file mode 100644 index 0000000..c8e4281 Binary files /dev/null and b/public/doctors.png differ