diff --git a/artifacts/dental-app/.replit-artifact/artifact.toml b/artifacts/dental-app/.replit-artifact/artifact.toml
new file mode 100644
index 0000000..07387fa
--- /dev/null
+++ b/artifacts/dental-app/.replit-artifact/artifact.toml
@@ -0,0 +1,27 @@
+kind = "web"
+previewPath = "/"
+title = "My Dental Management Software"
+version = "1.0.0"
+id = "artifacts/dental-app"
+router = "path"
+
+[[services]]
+name = "web"
+paths = [ "/" ]
+localPort = 24305
+
+[services.development]
+run = "pnpm --filter @workspace/dental-app run dev"
+
+[services.production]
+build = [ "pnpm", "--filter", "@workspace/dental-app", "run", "build" ]
+serve = "static"
+publicDir = "artifacts/dental-app/dist/public"
+
+[[services.production.rewrites]]
+from = "/*"
+to = "/index.html"
+
+[services.env]
+PORT = "24305"
+BASE_PATH = "/"
diff --git a/artifacts/dental-app/components.json b/artifacts/dental-app/components.json
new file mode 100644
index 0000000..3ff62cf
--- /dev/null
+++ b/artifacts/dental-app/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
\ No newline at end of file
diff --git a/artifacts/dental-app/index.html b/artifacts/dental-app/index.html
new file mode 100644
index 0000000..7a55250
--- /dev/null
+++ b/artifacts/dental-app/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ My Dental Management Software
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/artifacts/dental-app/package.json b/artifacts/dental-app/package.json
new file mode 100644
index 0000000..d78843a
--- /dev/null
+++ b/artifacts/dental-app/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "@workspace/dental-app",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --config vite.config.ts --host 0.0.0.0",
+ "build": "vite build --config vite.config.ts",
+ "serve": "vite preview --config vite.config.ts --host 0.0.0.0",
+ "typecheck": "tsc -p tsconfig.json --noEmit"
+ },
+ "devDependencies": {
+ "@hookform/resolvers": "^3.10.0",
+ "@radix-ui/react-accordion": "^1.2.4",
+ "@radix-ui/react-alert-dialog": "^1.1.7",
+ "@radix-ui/react-aspect-ratio": "^1.1.3",
+ "@radix-ui/react-avatar": "^1.1.4",
+ "@radix-ui/react-checkbox": "^1.1.5",
+ "@radix-ui/react-collapsible": "^1.1.4",
+ "@radix-ui/react-context-menu": "^2.2.7",
+ "@radix-ui/react-dialog": "^1.1.7",
+ "@radix-ui/react-dropdown-menu": "^2.1.7",
+ "@radix-ui/react-hover-card": "^1.1.7",
+ "@radix-ui/react-label": "^2.1.3",
+ "@radix-ui/react-menubar": "^1.1.7",
+ "@radix-ui/react-navigation-menu": "^1.2.6",
+ "@radix-ui/react-popover": "^1.1.7",
+ "@radix-ui/react-progress": "^1.1.3",
+ "@radix-ui/react-radio-group": "^1.2.4",
+ "@radix-ui/react-scroll-area": "^1.2.4",
+ "@radix-ui/react-select": "^2.1.7",
+ "@radix-ui/react-separator": "^1.1.3",
+ "@radix-ui/react-slider": "^1.2.4",
+ "@radix-ui/react-slot": "^1.2.0",
+ "@radix-ui/react-switch": "^1.1.4",
+ "@radix-ui/react-tabs": "^1.1.4",
+ "@radix-ui/react-toast": "^1.2.7",
+ "@radix-ui/react-toggle": "^1.1.3",
+ "@radix-ui/react-toggle-group": "^1.1.3",
+ "@radix-ui/react-tooltip": "^1.2.0",
+ "@replit/vite-plugin-cartographer": "catalog:",
+ "@replit/vite-plugin-dev-banner": "catalog:",
+ "@replit/vite-plugin-runtime-error-modal": "catalog:",
+ "@tailwindcss/typography": "^0.5.15",
+ "@tailwindcss/vite": "catalog:",
+ "@tanstack/react-query": "catalog:",
+ "@types/node": "catalog:",
+ "@types/react": "catalog:",
+ "@types/react-dom": "catalog:",
+ "@vitejs/plugin-react": "catalog:",
+ "@workspace/api-client-react": "workspace:*",
+ "class-variance-authority": "catalog:",
+ "clsx": "catalog:",
+ "cmdk": "^1.1.1",
+ "date-fns": "^3.6.0",
+ "embla-carousel-react": "^8.6.0",
+ "framer-motion": "catalog:",
+ "input-otp": "^1.4.2",
+ "lucide-react": "catalog:",
+ "next-themes": "^0.4.6",
+ "react": "catalog:",
+ "react-day-picker": "^9.11.1",
+ "react-dom": "catalog:",
+ "react-hook-form": "^7.55.0",
+ "react-icons": "^5.4.0",
+ "react-resizable-panels": "^2.1.7",
+ "recharts": "^2.15.2",
+ "sonner": "^2.0.7",
+ "tailwind-merge": "catalog:",
+ "tailwindcss": "catalog:",
+ "tw-animate-css": "^1.4.0",
+ "vaul": "^1.1.2",
+ "vite": "catalog:",
+ "wouter": "^3.3.5",
+ "zod": "catalog:"
+ }
+}
diff --git a/artifacts/dental-app/public/favicon.svg b/artifacts/dental-app/public/favicon.svg
new file mode 100644
index 0000000..4373d3c
--- /dev/null
+++ b/artifacts/dental-app/public/favicon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/artifacts/dental-app/public/robots.txt b/artifacts/dental-app/public/robots.txt
new file mode 100644
index 0000000..c2a49f4
--- /dev/null
+++ b/artifacts/dental-app/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Allow: /
diff --git a/artifacts/dental-app/src/App.tsx b/artifacts/dental-app/src/App.tsx
new file mode 100644
index 0000000..69e6039
--- /dev/null
+++ b/artifacts/dental-app/src/App.tsx
@@ -0,0 +1,42 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { Toaster } from '@/components/ui/toaster';
+import { TooltipProvider } from '@/components/ui/tooltip';
+
+import { Navbar } from '@/components/Navbar';
+import { Hero } from '@/components/Hero';
+import { Features } from '@/components/Features';
+import { HowItWorks } from '@/components/HowItWorks';
+import { StatsBar } from '@/components/StatsBar';
+import { FinalCTA } from '@/components/FinalCTA';
+import { Footer } from '@/components/Footer';
+
+const queryClient = new QueryClient();
+
+function LandingPage() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function App() {
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/artifacts/dental-app/src/components/Features.tsx b/artifacts/dental-app/src/components/Features.tsx
new file mode 100644
index 0000000..dba0d63
--- /dev/null
+++ b/artifacts/dental-app/src/components/Features.tsx
@@ -0,0 +1,91 @@
+import { motion } from "framer-motion";
+import { MessageSquare, Clock, Shield } from "lucide-react";
+
+export function Features() {
+ const containerVariants = {
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: { staggerChildren: 0.2 }
+ }
+ };
+
+ const itemVariants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: { opacity: 1, y: 0, transition: { duration: 0.6 } }
+ };
+
+ return (
+
+
+
+
+ A New Standard for Practice
+
+
+ Everything you need to run your office, handled intuitively.
+
+
+
+
+ {/* Feature 1 */}
+
+
+
+
+
+ Real Intuitive Interface
+
+
+ Chat with our AI agent to check eligibility and claims using your own words. No clicking around. Increase your work efficiency more than 10 times.
+
+
+
+ {/* Feature 2 */}
+
+
+
+
+
+ 24/7 Patient Connection
+
+
+ After-hours messaging and calling handled automatically by AI, in your way. Never miss any patient inquiry.
+
+
+
+ {/* Feature 3 */}
+
+
+
+
+
+ Your Data, Your Control
+
+
+ All patient data lives on your local computers. Automatic local and remote backup — always secure, always yours.
+
+
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/FinalCTA.tsx b/artifacts/dental-app/src/components/FinalCTA.tsx
new file mode 100644
index 0000000..16cf861
--- /dev/null
+++ b/artifacts/dental-app/src/components/FinalCTA.tsx
@@ -0,0 +1,34 @@
+import { motion } from "framer-motion";
+import { Button } from "@/components/ui/button";
+
+export function FinalCTA() {
+ return (
+
+ {/* Decorative circle */}
+
+
+
+
+
+ Ready to Transform Your Practice?
+
+
+ Join dental offices discovering smarter administration.
+
+
+ Start for Free Today
+
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/Footer.tsx b/artifacts/dental-app/src/components/Footer.tsx
new file mode 100644
index 0000000..d98b9b2
--- /dev/null
+++ b/artifacts/dental-app/src/components/Footer.tsx
@@ -0,0 +1,46 @@
+export function Footer() {
+ return (
+
+
+
+
+
+
+
+ My Dental Software
+
+
+
Chat and Go.
+
+
+
+
+
+
+
© {new Date().getFullYear()} My Dental Management Software. All rights reserved.
+
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/Hero.tsx b/artifacts/dental-app/src/components/Hero.tsx
new file mode 100644
index 0000000..f2df58d
--- /dev/null
+++ b/artifacts/dental-app/src/components/Hero.tsx
@@ -0,0 +1,124 @@
+import { motion } from "framer-motion";
+import { Button } from "@/components/ui/button";
+
+export function Hero() {
+ return (
+
+ {/* Decorative background shapes */}
+
+
+
+
+
+
+ Chat and Go.
+
+
+
+ No CDT Code Search. No Paying for Third Parties.
+
+
+
+ Complete your administrative tasks by using your natural language — just type what you need, and our AI reads it, gathers the required data, and gets it done automatically.
+
+
+
+
+ Get Started Free
+
+
+ Book a Demo
+
+
+
+
+ {/* Mock UI preview */}
+
+
+
+
+
+
+
my-dental-software.app
+
+
+
+
+
+
+
+ Check if John Smith is eligible for a crown
+
+
+
+
+ Done.
+ John Smith (DOB: 10/12/1985) is eligible for D2740 (Crown - porcelain/ceramic) through Delta Dental. Copay is 20%. Remaining annual maximum: $1,250.
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/HowItWorks.tsx b/artifacts/dental-app/src/components/HowItWorks.tsx
new file mode 100644
index 0000000..fd0f055
--- /dev/null
+++ b/artifacts/dental-app/src/components/HowItWorks.tsx
@@ -0,0 +1,90 @@
+import { motion } from "framer-motion";
+
+export function HowItWorks() {
+ const steps = [
+ {
+ num: "01",
+ title: "Type your task in plain English",
+ desc: '"Check if John Smith is eligible for a crown"'
+ },
+ {
+ num: "02",
+ title: "Our AI reads it",
+ desc: "Looks up the data, and handles the task automatically"
+ },
+ {
+ num: "03",
+ title: "Get results instantly",
+ desc: "No CDT code lookup, no third-party logins"
+ }
+ ];
+
+ return (
+
+
+
+
+
+
+ How It Works
+
+
+ Forget endless menus and nested screens. We built a system that understands what you want to do.
+
+
+
+ {steps.map((step, idx) => (
+
+
+ {step.num}
+
+
+
{step.title}
+
{step.desc}
+
+
+ ))}
+
+
+
+
+
+
+
+ {/* Chat Mockup */}
+
+ Can you pull up today's schedule and highlight any cancellations?
+
+
+
+
Assistant
+ You have 12 appointments today. One cancellation at 2:00 PM (Jane Doe - Cleaning). I've already texted the waitlist.
+
+
+ Great, file the claim for Michael's extraction yesterday.
+
+
+
+
Assistant
+ Claim filed for Michael (D7140) to MetLife. Sent at 9:14 AM.
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/Navbar.tsx b/artifacts/dental-app/src/components/Navbar.tsx
new file mode 100644
index 0000000..151a300
--- /dev/null
+++ b/artifacts/dental-app/src/components/Navbar.tsx
@@ -0,0 +1,48 @@
+import { Button } from "@/components/ui/button";
+
+export function Navbar() {
+ const scrollTo = (id: string) => {
+ const el = document.getElementById(id);
+ if (el) {
+ el.scrollIntoView({ behavior: 'smooth' });
+ }
+ };
+
+ return (
+
+
+
scrollTo('hero')}>
+
+
+ My Dental Software
+
+
+
+
+ scrollTo('features')}
+ className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors"
+ data-testid="nav-features"
+ >
+ Features
+
+ scrollTo('how-it-works')}
+ className="text-sm font-medium text-muted-foreground hover:text-primary transition-colors"
+ data-testid="nav-how-it-works"
+ >
+ How It Works
+
+
+ Get Started Free
+
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/StatsBar.tsx b/artifacts/dental-app/src/components/StatsBar.tsx
new file mode 100644
index 0000000..ee9e683
--- /dev/null
+++ b/artifacts/dental-app/src/components/StatsBar.tsx
@@ -0,0 +1,37 @@
+import { motion } from "framer-motion";
+
+export function StatsBar() {
+ const stats = [
+ { value: "10x", label: "Work Efficiency" },
+ { value: "24/7", label: "Patient Coverage" },
+ { value: "100%", label: "Local Data Control" },
+ { value: "Zero", label: "CDT Code Lookups" }
+ ];
+
+ return (
+
+
+
+ {stats.map((stat, idx) => (
+
+
+ {stat.value}
+
+
+ {stat.label}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/artifacts/dental-app/src/components/ui/accordion.tsx b/artifacts/dental-app/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..5ad3b62
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/accordion.tsx
@@ -0,0 +1,54 @@
+import * as React from 'react';
+import * as AccordionPrimitive from '@radix-ui/react-accordion';
+import { cn } from '@/lib/utils';
+import { ChevronDown } from 'lucide-react';
+
+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/artifacts/dental-app/src/components/ui/alert-dialog.tsx b/artifacts/dental-app/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..82985dc
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,138 @@
+import * as React from 'react';
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
+import { buttonVariants } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+
+const AlertDialog = AlertDialogPrimitive.Root;
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal;
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+));
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+AlertDialogHeader.displayName = 'AlertDialogHeader';
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+AlertDialogFooter.displayName = 'AlertDialogFooter';
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName;
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/artifacts/dental-app/src/components/ui/alert.tsx b/artifacts/dental-app/src/components/ui/alert.tsx
new file mode 100644
index 0000000..8bdb5e1
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/alert.tsx
@@ -0,0 +1,58 @@
+import * as React from 'react';
+import { cn } from '@/lib/utils';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+const alertVariants = cva(
+ 'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
+ {
+ variants: {
+ variant: {
+ default: 'bg-background text-foreground',
+ destructive:
+ 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+);
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+));
+Alert.displayName = 'Alert';
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+AlertTitle.displayName = 'AlertTitle';
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+AlertDescription.displayName = 'AlertDescription';
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/artifacts/dental-app/src/components/ui/aspect-ratio.tsx b/artifacts/dental-app/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..5dfdf1e
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio';
+
+const AspectRatio = AspectRatioPrimitive.Root;
+
+export { AspectRatio };
diff --git a/artifacts/dental-app/src/components/ui/avatar.tsx b/artifacts/dental-app/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..e1cde8e
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/avatar.tsx
@@ -0,0 +1,49 @@
+'use client';
+
+import * as React from 'react';
+import * as AvatarPrimitive from '@radix-ui/react-avatar';
+import { cn } from '@/lib/utils';
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+Avatar.displayName = AvatarPrimitive.Root.displayName;
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AvatarImage.displayName = AvatarPrimitive.Image.displayName;
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/artifacts/dental-app/src/components/ui/badge.tsx b/artifacts/dental-app/src/components/ui/badge.tsx
new file mode 100644
index 0000000..4cd8053
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/badge.tsx
@@ -0,0 +1,42 @@
+import * as React from 'react';
+import { cn } from '@/lib/utils';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+const badgeVariants = cva(
+ // @replit
+ // Whitespace-nowrap: Badges should never wrap.
+ 'whitespace-nowrap inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2' +
+ ' hover-elevate ',
+ {
+ variants: {
+ variant: {
+ default:
+ // @replit shadow-xs instead of shadow, no hover because we use hover-elevate
+ 'border-transparent bg-primary text-primary-foreground shadow-xs',
+ secondary:
+ // @replit no hover because we use hover-elevate
+ 'border-transparent bg-secondary text-secondary-foreground',
+ destructive:
+ // @replit shadow-xs instead of shadow, no hover because we use hover-elevate
+ 'border-transparent bg-destructive text-destructive-foreground shadow-xs',
+ // @replit shadow-xs" - use badge outline variable
+ outline: 'text-foreground border [border-color:var(--badge-outline)]',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+);
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/artifacts/dental-app/src/components/ui/breadcrumb.tsx b/artifacts/dental-app/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..2e43292
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,114 @@
+import * as React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cn } from '@/lib/utils';
+import { ChevronRight, MoreHorizontal } from 'lucide-react';
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<'nav'> & {
+ separator?: React.ReactNode;
+ }
+>(({ ...props }, ref) => );
+Breadcrumb.displayName = 'Breadcrumb';
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<'ol'>
+>(({ className, ...props }, ref) => (
+
+));
+BreadcrumbList.displayName = 'BreadcrumbList';
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<'li'>
+>(({ className, ...props }, ref) => (
+
+));
+BreadcrumbItem.displayName = 'BreadcrumbItem';
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<'a'> & {
+ asChild?: boolean;
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'a';
+
+ return (
+
+ );
+});
+BreadcrumbLink.displayName = 'BreadcrumbLink';
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<'span'>
+>(({ className, ...props }, ref) => (
+
+));
+BreadcrumbPage.displayName = 'BreadcrumbPage';
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<'li'>) => (
+ svg]:w-3.5 [&>svg]:h-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
+);
+BreadcrumbSeparator.displayName = 'BreadcrumbSeparator';
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) => (
+
+
+ More
+
+);
+BreadcrumbEllipsis.displayName = 'BreadcrumbElipssis';
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/artifacts/dental-app/src/components/ui/button-group.tsx b/artifacts/dental-app/src/components/ui/button-group.tsx
new file mode 100644
index 0000000..90c86e4
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/button-group.tsx
@@ -0,0 +1,82 @@
+import { Slot } from '@radix-ui/react-slot';
+import { Separator } from '@/components/ui/separator';
+import { cn } from '@/lib/utils';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ '[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none',
+ vertical:
+ 'flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none',
+ },
+ },
+ defaultVariants: {
+ orientation: 'horizontal',
+ },
+ },
+);
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<'div'> & VariantProps) {
+ return (
+
+ );
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : 'div';
+
+ return (
+
+ );
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = 'vertical',
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+};
diff --git a/artifacts/dental-app/src/components/ui/button.tsx b/artifacts/dental-app/src/components/ui/button.tsx
new file mode 100644
index 0000000..c9c71de
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+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 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 }
\ No newline at end of file
diff --git a/artifacts/dental-app/src/components/ui/calendar.tsx b/artifacts/dental-app/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..3686a1b
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/calendar.tsx
@@ -0,0 +1,212 @@
+'use client';
+
+import * as React from 'react';
+import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
+import { Button, buttonVariants } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from 'lucide-react';
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = 'label',
+ buttonVariant = 'ghost',
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps['variant'];
+}) {
+ const defaultClassNames = getDefaultClassNames();
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className,
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString('default', { month: 'short' }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn('w-fit', defaultClassNames.root),
+ months: cn(
+ 'relative flex flex-col gap-4 md:flex-row',
+ defaultClassNames.months,
+ ),
+ month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
+ nav: cn(
+ 'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',
+ defaultClassNames.nav,
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
+ defaultClassNames.button_previous,
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ 'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
+ defaultClassNames.button_next,
+ ),
+ month_caption: cn(
+ 'flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]',
+ defaultClassNames.month_caption,
+ ),
+ dropdowns: cn(
+ 'flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium',
+ defaultClassNames.dropdowns,
+ ),
+ dropdown_root: cn(
+ 'has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border',
+ defaultClassNames.dropdown_root,
+ ),
+ dropdown: cn(
+ 'bg-popover absolute inset-0 opacity-0',
+ defaultClassNames.dropdown,
+ ),
+ caption_label: cn(
+ 'select-none font-medium',
+ captionLayout === 'label'
+ ? 'text-sm'
+ : '[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5',
+ defaultClassNames.caption_label,
+ ),
+ table: 'w-full border-collapse',
+ weekdays: cn('flex', defaultClassNames.weekdays),
+ weekday: cn(
+ 'text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal',
+ defaultClassNames.weekday,
+ ),
+ week: cn('mt-2 flex w-full', defaultClassNames.week),
+ week_number_header: cn(
+ 'w-[--cell-size] select-none',
+ defaultClassNames.week_number_header,
+ ),
+ week_number: cn(
+ 'text-muted-foreground select-none text-[0.8rem]',
+ defaultClassNames.week_number,
+ ),
+ day: cn(
+ 'group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md',
+ defaultClassNames.day,
+ ),
+ range_start: cn(
+ 'bg-accent rounded-l-md',
+ defaultClassNames.range_start,
+ ),
+ range_middle: cn('rounded-none', defaultClassNames.range_middle),
+ range_end: cn('bg-accent rounded-r-md', defaultClassNames.range_end),
+ today: cn(
+ 'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
+ defaultClassNames.today,
+ ),
+ outside: cn(
+ 'text-muted-foreground aria-selected:text-muted-foreground',
+ defaultClassNames.outside,
+ ),
+ disabled: cn(
+ 'text-muted-foreground opacity-50',
+ defaultClassNames.disabled,
+ ),
+ hidden: cn('invisible', defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ );
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === 'left') {
+ return (
+
+ );
+ }
+
+ if (orientation === 'right') {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+
+ );
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ );
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames();
+
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus();
+ }, [modifiers.focused]);
+
+ return (
+ span]:text-xs [&>span]:opacity-70',
+ defaultClassNames.day,
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+export { Calendar, CalendarDayButton };
diff --git a/artifacts/dental-app/src/components/ui/card.tsx b/artifacts/dental-app/src/components/ui/card.tsx
new file mode 100644
index 0000000..bc1c974
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/card.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+import { cn } from '@/lib/utils';
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Card.displayName = 'Card';
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardHeader.displayName = 'CardHeader';
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardTitle.displayName = 'CardTitle';
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardDescription.displayName = 'CardDescription';
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardContent.displayName = 'CardContent';
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardFooter.displayName = 'CardFooter';
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardDescription,
+ CardContent,
+};
diff --git a/artifacts/dental-app/src/components/ui/carousel.tsx b/artifacts/dental-app/src/components/ui/carousel.tsx
new file mode 100644
index 0000000..b705b6b
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/carousel.tsx
@@ -0,0 +1,259 @@
+import * as React from 'react';
+import { Button } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from 'embla-carousel-react';
+import { ArrowLeft, ArrowRight } from 'lucide-react';
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: 'horizontal' | 'vertical';
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error('useCarousel must be used within a ');
+ }
+
+ return context;
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
+>(
+ (
+ {
+ orientation = 'horizontal',
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref,
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === 'horizontal' ? 'x' : 'y',
+ },
+ plugins,
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return;
+ }
+
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === 'ArrowLeft') {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === 'ArrowRight') {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext],
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return;
+ }
+
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) {
+ return;
+ }
+
+ onSelect(api);
+ api.on('reInit', onSelect);
+ api.on('select', onSelect);
+
+ return () => {
+ api?.off('select', onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+ },
+);
+Carousel.displayName = 'Carousel';
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+});
+CarouselContent.displayName = 'CarouselContent';
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+});
+CarouselItem.displayName = 'CarouselItem';
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+});
+CarouselPrevious.displayName = 'CarouselPrevious';
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = 'outline', size = 'icon', ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+});
+CarouselNext.displayName = 'CarouselNext';
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/artifacts/dental-app/src/components/ui/chart.tsx b/artifacts/dental-app/src/components/ui/chart.tsx
new file mode 100644
index 0000000..b71c184
--- /dev/null
+++ b/artifacts/dental-app/src/components/ui/chart.tsx
@@ -0,0 +1,366 @@
+import * as React from 'react';
+import { cn } from '@/lib/utils';
+import * as RechartsPrimitive from 'recharts';
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: '', dark: '.dark' } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error('useChart must be used within a ');
+ }
+
+ return context;
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<'div'> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >['children'];
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+});
+ChartContainer.displayName = 'Chart';
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+