diff --git a/.gitignore b/.gitignore index 5249dec..bc1624f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules .pnp .pnp.js + # Local env files .env .env.local diff --git a/.npmrc b/.npmrc index 9d774d2..e69de29 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +0,0 @@ -public-hoist-pattern[]=* \ No newline at end of file diff --git a/apps/app/app/(routes)/[slug]/page.tsx b/apps/app/app/(routes)/[slug]/page.tsx new file mode 100644 index 0000000..07e9d6d --- /dev/null +++ b/apps/app/app/(routes)/[slug]/page.tsx @@ -0,0 +1,125 @@ +"use client"; +import { IconCoins, IconInfoSquareRounded } from "@tabler/icons-react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + +export default function Home() { + return ( +
+ + + Overview + Analytics + Reports + Notifications + + +
+ + + + Queries Resolved + + + + +
45,321
+

+ +20.1% from last month +

+
+
+ + + Mails + + + + + + + +
+2350
+

+ +180.1% from last month +

+
+
+ + + Sales + + + + + + +
+12,234
+

+ +19% from last month +

+
+
+ + + + Events Logged + + + + + + +
+573
+

+ +201 since last hour +

+
+
+ + + + Credits Left + + + + +
2346
+

+ +201 since last hour +

+
+
+
+
+
+
+ ); +} diff --git a/apps/app/app/(routes)/settings/page.tsx b/apps/app/app/(routes)/settings/page.tsx new file mode 100644 index 0000000..b5d6b0e --- /dev/null +++ b/apps/app/app/(routes)/settings/page.tsx @@ -0,0 +1,17 @@ +"use client"; +import InfoBreadCrumb from "@/components/custom/infobar/bread-crumb"; +import BillingSettings from "@/components/custom/settings/billing.settings"; +import ThemeSettings from "@/components/custom/settings/theme.settings"; +import React from "react"; + +export default function SettingsPage() { + return ( +
+ +
+ + +
+
+ ); +} diff --git a/apps/app/app/fonts/GeistMonoVF.woff b/apps/app/app/fonts/GeistMonoVF.woff deleted file mode 100644 index f2ae185..0000000 Binary files a/apps/app/app/fonts/GeistMonoVF.woff and /dev/null differ diff --git a/apps/app/app/fonts/GeistVF.woff b/apps/app/app/fonts/GeistVF.woff deleted file mode 100644 index 1b62daa..0000000 Binary files a/apps/app/app/fonts/GeistVF.woff and /dev/null differ diff --git a/apps/app/app/globals.css b/apps/app/app/globals.css index 6b717ad..19c1fdd 100644 --- a/apps/app/app/globals.css +++ b/apps/app/app/globals.css @@ -2,20 +2,122 @@ @tailwind components; @tailwind utilities; -:root { - --background: #ffffff; - --foreground: #171717; +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + --radius: 0.5rem; + + --progress-background: hsl(var(--primary)); + --progress-foreground: hsl(var(--primary-foreground)); + + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + --color-1: 0 100% 63%; + --color-2: 270 100% 63%; + --color-3: 210 100% 63%; + --color-4: 195 100% 63%; + --color-5: 90 100% 63%; + } + + .dark { + --background: 0, 0%, 9.8%; + --foreground: 0 0% 98%; + --card: 0, 0%, 14.5%; + --card-foreground: 0 0% 98%; + --popover: 0, 0%, 14.5%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 0, 0%, 19.2%; + --secondary-foreground: 0 0% 98%; + --muted: 0, 0%, 19.2%; + --muted-foreground: 240 5% 64.9%; + --accent: 0, 0%, 19.2%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0, 0%, 19.2%; + --input: 0, 0%, 19.2%; + --ring: 240 4.9% 83.9%; + + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + + --sidebar-background: 0, 0%, 12.5%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 0, 0%, 19.2%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 0, 0%, 19.2%; + --sidebar-ring: 217.2 91.2% 59.8%; + --color-1: 0 100% 63%; + --color-2: 270 100% 63%; + --color-3: 210 100% 63%; + --color-4: 195 100% 63%; + --color-5: 90 100% 63%; + } } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; +@layer base { + /* * { + @apply border-border; + } */ + html { + @apply scroll-smooth; + } + body { + @apply bg-background text-foreground; + /* font-feature-settings: "rlig" 1, "calt" 1; */ + font-synthesis-weight: none; + text-rendering: optimizeLegibility; } } -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; +@media (max-width: 640px) { + .container { + @apply px-4; + } } + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/apps/app/app/layout.tsx b/apps/app/app/layout.tsx index a36cde0..3bbcb42 100644 --- a/apps/app/app/layout.tsx +++ b/apps/app/app/layout.tsx @@ -1,35 +1,49 @@ import type { Metadata } from "next"; -import localFont from "next/font/local"; +import { GeistSans } from "geist/font/sans"; import "./globals.css"; - -const geistSans = localFont({ - src: "./fonts/GeistVF.woff", - variable: "--font-geist-sans", - weight: "100 900", -}); -const geistMono = localFont({ - src: "./fonts/GeistMonoVF.woff", - variable: "--font-geist-mono", - weight: "100 900", -}); +import { ThemeProvider } from "@/hooks/theme-provider"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { AppSidebar } from "@/components/custom/sidebar/sidebar"; +import { cookies } from "next/headers"; +import Infobar from "@/components/custom/infobar/infobar"; +import ProgressBar from "@/components/custom/progress.bar"; export const metadata: Metadata = { - title: "Create Next App", + title: "Plura", description: "Generated by create next app", }; -export default function RootLayout({ +async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + const cookieStore = await cookies(); + const defaultOpen = cookieStore.get("plura-sidebar:state")?.value === "true"; + return ( - + - {children} + + + +
+ + + {children} +
+
+
); } + +export default RootLayout; diff --git a/apps/app/app/page.tsx b/apps/app/app/page.tsx deleted file mode 100644 index 9007252..0000000 --- a/apps/app/app/page.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
- -
- - Vercel logomark - Deploy now - - - Read our docs - -
-
- -
- ); -} diff --git a/apps/app/components.json b/apps/app/components.json new file mode 100644 index 0000000..a312865 --- /dev/null +++ b/apps/app/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/apps/app/components/custom/dashboard/stats.tsx b/apps/app/components/custom/dashboard/stats.tsx new file mode 100644 index 0000000..e5e0aea --- /dev/null +++ b/apps/app/components/custom/dashboard/stats.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export default function DashboardStats() { + return
; +} diff --git a/apps/app/components/custom/infobar/bread-crumb.tsx b/apps/app/components/custom/infobar/bread-crumb.tsx new file mode 100644 index 0000000..a07a343 --- /dev/null +++ b/apps/app/components/custom/infobar/bread-crumb.tsx @@ -0,0 +1,40 @@ +import { usePathname } from "next/navigation"; +import React from "react"; + +export default function InfoBreadCrumb() { + const page = usePathname(); + return ( +
+
+

+ {page.replace(/^\/+/, "")} +

+ {/* {page === 'conversation' && chatRoom && ( + + onActivateRealtime(e)} + className="data-[state=checked]:bg-orange data-[state=unchecked]:bg-peach" + /> + + )} */} +
+

+ {page == "settings" + ? "Manage your account settings, preferences and integrations" + : page == "dashboard" + ? "A detailed overview of your metrics, usage, customers and more" + : page == "appointment" + ? "View and edit all your appointments" + : page == "email-marketing" + ? "Send bulk emails to your customers" + : page == "integration" + ? "Connect third-party applications into Corinna-AI" + : "Modify domain settings, change chatbot options, enter sales questions and train your bot to do what you want it to."} +

+
+ ); +} diff --git a/apps/app/components/custom/infobar/infobar.tsx b/apps/app/components/custom/infobar/infobar.tsx new file mode 100644 index 0000000..3fd52cf --- /dev/null +++ b/apps/app/components/custom/infobar/infobar.tsx @@ -0,0 +1,201 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { SidebarTrigger } from "@/components/ui/sidebar"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Check, ChevronsUpDown, Slash } from "lucide-react"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; + +const frameworks = [ + { + value: "next.js", + label: "Next.js", + }, + { + value: "sveltekit", + label: "SvelteKit", + }, + { + value: "nuxt.js", + label: "Nuxt.js", + }, + { + value: "remix", + label: "Remix", + }, + { + value: "astro", + label: "Astro", + }, +]; + +export default function Infobar() { + const [isScrolled, setIsScrolled] = useState(false); + const [openPopover1, setOpenPopover1] = useState(false); + const [openPopover2, setOpenPopover2] = useState(false); + const [value, setValue] = useState(""); + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 0); + }; + + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + return ( + + ); +} diff --git a/apps/app/components/custom/loader.tsx b/apps/app/components/custom/loader.tsx new file mode 100644 index 0000000..46f0856 --- /dev/null +++ b/apps/app/components/custom/loader.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { motion, Variants } from "framer-motion"; + +const containerVariants: Variants = { + animate: { + transition: { + staggerChildren: 0.25, + }, + }, +}; + +const itemVariants: Variants = { + initial: { + scaleY: 0.5, + opacity: 0, + }, + animate: { + scaleY: 1, + opacity: 1, + transition: { + repeat: Infinity, + repeatType: "mirror", + duration: 1, + ease: "circIn", + }, + }, +}; + +export default function LoaderAnim() { + return ( + + {[...Array(5)].map((_, index) => ( + + ))} + + ); +} diff --git a/apps/app/components/custom/progress.bar.tsx b/apps/app/components/custom/progress.bar.tsx new file mode 100644 index 0000000..991e1ad --- /dev/null +++ b/apps/app/components/custom/progress.bar.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { usePathname, useSearchParams } from "next/navigation"; +import { Progress } from "@/components/ui/progress"; + +export default function ProgressBar() { + const [progress, setProgress] = useState(0); + const [isVisible, setIsVisible] = useState(false); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + // Function to start progress when navigation begins + const startProgress = useCallback(() => { + setProgress(0); + setIsVisible(true); + }, []); + + // Function to complete the progress bar with a delay for smoothness + const completeProgress = useCallback(() => { + setProgress(100); + const timeout = setTimeout(() => { + setIsVisible(false); + setProgress(0); + }, 500); + return () => clearTimeout(timeout); + }, []); + + useEffect(() => { + startProgress(); + + // Gradually increase progress to give a smooth loading effect + const interval = setInterval(() => { + setProgress((prev) => { + if (prev < 90) { + return prev + 10; // Adjust the increment and speed as needed + } + return prev; + }); + }, 200); // Update every 200ms for smoothness + + // Complete the progress on route change completion + const complete = setTimeout(() => { + completeProgress(); + clearInterval(interval); + }, 800); // Total time for the progress to complete + + return () => { + clearTimeout(complete); + clearInterval(interval); + }; + }, [pathname, searchParams, startProgress, completeProgress]); + + if (!isVisible) return null; + + return ( + + ); +} diff --git a/apps/app/components/custom/section/section.label.tsx b/apps/app/components/custom/section/section.label.tsx new file mode 100644 index 0000000..f5da5e3 --- /dev/null +++ b/apps/app/components/custom/section/section.label.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +interface SectionProps { + label: string; + msg: string; +} +export default function SectionLabel({ label, msg }: SectionProps) { + return ( +
+

{label}

+

{msg}

+
+ ); +} diff --git a/apps/app/components/custom/settings/billing.settings.tsx b/apps/app/components/custom/settings/billing.settings.tsx new file mode 100644 index 0000000..11a3807 --- /dev/null +++ b/apps/app/components/custom/settings/billing.settings.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import SectionLabel from "../section/section.label"; +import { Card, CardContent, CardDescription } from "@/components/ui/card"; +import { CheckCircle2, Plus } from "lucide-react"; + +export default function BillingSettings() { + return ( +
+
+ +
+
+ + +
+ +
+ + Upgrade Plan + +
+
+
+
+

Current Plan

+

Freemium

+
+
+ +

200 Credits

+
+
+ +

2 Domains

+
+
+
+
+ ); +} diff --git a/apps/app/components/custom/settings/theme.settings.tsx b/apps/app/components/custom/settings/theme.settings.tsx new file mode 100644 index 0000000..0267cfd --- /dev/null +++ b/apps/app/components/custom/settings/theme.settings.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/lib/utils"; +import SectionLabel from "../section/section.label"; +import { SystemMode } from "@/components/icons/themes/system"; +import { LightMode } from "@/components/icons/themes/light"; +import { DarkMode } from "@/components/icons/themes/dark"; +import BlurFade from "@/components/ui/blur-fade"; + +export default function Component() { + const { setTheme, theme } = useTheme(); + + return ( +
+
+ +
+ + {theme ? ( + <> +
setTheme("system")} + > + +
+
setTheme("light")} + > + +
+
setTheme("dark")} + > + +
+ + ) : ( +
+ {[...Array(3)].map((_, i) => ( + +
+
+
+
+
+ + ))} +
+ )} + +
+ ); +} diff --git a/apps/app/components/custom/sidebar/sidebar.tsx b/apps/app/components/custom/sidebar/sidebar.tsx new file mode 100644 index 0000000..5ffcb4c --- /dev/null +++ b/apps/app/components/custom/sidebar/sidebar.tsx @@ -0,0 +1,170 @@ +"use client"; +import { + Webhook, + ChevronDown, + Layers2, + Waypoints, + Mails, + Settings, + Brain, + BrainCircuit, + FlaskConical, + ArchiveRestore, + Codepen, +} from "lucide-react"; + +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { usePathname } from "next/navigation"; + +// Menu items. +const items = [ + { + title: "Dashboard", + url: "/home", + icon: Layers2, + }, + { + title: "Integrations", + url: "/integrations", + icon: Waypoints, + }, + { + title: "Events", + url: "/events", + icon: Webhook, + }, + { + title: "Mails", + url: "#", + icon: Mails, + }, + { + title: "Settings", + url: "/settings", + icon: Settings, + }, +]; + +// Playground items. +const IntelItems = [ + { + title: "Agents", + url: "/agents", + icon: Brain, + }, + { + title: "Memory", + url: "/memory", + icon: BrainCircuit, + }, + { + title: "Playground", + url: "/playground", + icon: FlaskConical, + }, + { + title: "Components", + url: "/components", + icon: Codepen, + }, + { + title: "Archives", + url: "/archives", + icon: ArchiveRestore, + }, +]; + +export function AppSidebar() { + const path = usePathname(); + + return ( + + + + +
+ + + + Switch Account + + + + + + Acme Inc + + + Acme Corp. + + + +
+
+
+
+ + + Application + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + Intelligence + + + {IntelItems.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + + + + +
+ ); +} diff --git a/apps/app/components/icons/themes/dark.tsx b/apps/app/components/icons/themes/dark.tsx new file mode 100644 index 0000000..a134692 --- /dev/null +++ b/apps/app/components/icons/themes/dark.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +export const DarkMode = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/app/components/icons/themes/light.tsx b/apps/app/components/icons/themes/light.tsx new file mode 100644 index 0000000..6a131d2 --- /dev/null +++ b/apps/app/components/icons/themes/light.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +export const LightMode = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/app/components/icons/themes/system.tsx b/apps/app/components/icons/themes/system.tsx new file mode 100644 index 0000000..81165ad --- /dev/null +++ b/apps/app/components/icons/themes/system.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +export const SystemMode = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/app/components/ui/alert-dialog.tsx b/apps/app/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..f69596f --- /dev/null +++ b/apps/app/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client"; + +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +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/apps/app/components/ui/alert.tsx b/apps/app/components/ui/alert.tsx new file mode 100644 index 0000000..1df0436 --- /dev/null +++ b/apps/app/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +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/apps/app/components/ui/avatar.tsx b/apps/app/components/ui/avatar.tsx new file mode 100644 index 0000000..09cd14d --- /dev/null +++ b/apps/app/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"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/apps/app/components/ui/badge.tsx b/apps/app/components/ui/badge.tsx new file mode 100644 index 0000000..f795980 --- /dev/null +++ b/apps/app/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "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", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/apps/app/components/ui/blur-fade.tsx b/apps/app/components/ui/blur-fade.tsx new file mode 100644 index 0000000..44e897e --- /dev/null +++ b/apps/app/components/ui/blur-fade.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { useRef } from "react"; +import { + AnimatePresence, + motion, + useInView, + UseInViewOptions, + Variants, +} from "framer-motion"; + +type MarginType = UseInViewOptions["margin"]; + +interface BlurFadeProps { + children: React.ReactNode; + className?: string; + variant?: { + hidden: { y: number }; + visible: { y: number }; + }; + duration?: number; + delay?: number; + yOffset?: number; + inView?: boolean; + inViewMargin?: MarginType; + blur?: string; +} + +export default function BlurFade({ + children, + className, + variant, + duration = 0.4, + delay = 0, + yOffset = 6, + inView = false, + inViewMargin = "-50px", + blur = "6px", +}: BlurFadeProps) { + const ref = useRef(null); + const inViewResult = useInView(ref, { once: true, margin: inViewMargin }); + const isInView = !inView || inViewResult; + const defaultVariants: Variants = { + hidden: { y: yOffset, opacity: 0, filter: `blur(${blur})` }, + visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` }, + }; + const combinedVariants = variant || defaultVariants; + return ( + + + {children} + + + ); +} diff --git a/apps/app/components/ui/breadcrumb.tsx b/apps/app/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..ecfc6a4 --- /dev/null +++ b/apps/app/components/ui/breadcrumb.tsx @@ -0,0 +1,115 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>