diff --git a/.cursor/rules/craftshop-core.mdc b/.cursor/rules/craftshop-core.mdc index 0075602..b91e9f1 100644 --- a/.cursor/rules/craftshop-core.mdc +++ b/.cursor/rules/craftshop-core.mdc @@ -13,7 +13,7 @@ alwaysApply: true - **Frontend**: Vite + React + TypeScript, axios, @tanstack/react-query, MUI. - **Архитектура фронта**: **FSD** (`app/pages/widgets/features/entities/shared`), alias `@` → `client/src`. - **Backend**: Node.js + Fastify + Prisma + SQLite. -- Данные управляются через фронтенд‑админку, админ‑API защищено `Authorization: Bearer `. +- Данные управляются через фронтенд‑админку; доступ к админ‑API проверяется серверным `verifyAdmin` (JWT пользователя + совпадение `request.user.email` с `ADMIN_EMAIL`). ## Правила работы с кодом - Всегда придерживаться **FSD границ**: нижние слои не импортируют верхние. diff --git a/.cursor/rules/frontend-devserver-proxy.mdc b/.cursor/rules/frontend-devserver-proxy.mdc new file mode 100644 index 0000000..11fefa1 --- /dev/null +++ b/.cursor/rules/frontend-devserver-proxy.mdc @@ -0,0 +1,12 @@ +--- +description: Актуальные требования к Vite proxy для локальной разработки +globs: client/vite.config.ts +alwaysApply: false +--- + +# Frontend Dev Server Proxy + +- В `client/vite.config.ts` должны проксироваться и API, и загрузки файлов. +- Обязательные прокси: + - `'/api' -> 'http://127.0.0.1:3333'` + - `'/uploads' -> 'http://127.0.0.1:3333'` diff --git a/.cursor/rules/frontend-rich-text.mdc b/.cursor/rules/frontend-rich-text.mdc new file mode 100644 index 0000000..cb9630e --- /dev/null +++ b/.cursor/rules/frontend-rich-text.mdc @@ -0,0 +1,13 @@ +--- +description: Правила использования RichTextMessageContent (TipTap) на фронтенде +globs: client/src/**/*.tsx +alwaysApply: false +--- + +# Frontend Rich Text (TipTap) + +- Для отображения rich text использовать общий компонент `shared/ui/RichTextMessageContent`. +- Не дублировать стили ProseMirror локально на страницах и в виджетах без необходимости. +- Для контекста отзывов передавать `tone="review"`. +- Для переписок по заказам передавать `tone="chat"`. +- `tone="default"` использовать только в нейтральных/общих сценариях. diff --git a/Untitled/ATTRIBUTIONS.md b/Untitled/ATTRIBUTIONS.md new file mode 100644 index 0000000..5df5c40 --- /dev/null +++ b/Untitled/ATTRIBUTIONS.md @@ -0,0 +1,3 @@ +This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md). + +This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license). diff --git a/Untitled/README.md b/Untitled/README.md new file mode 100644 index 0000000..b4e4e92 --- /dev/null +++ b/Untitled/README.md @@ -0,0 +1,11 @@ + + # Untitled + + This is a code bundle for Untitled. The original project is available at https://www.figma.com/design/3LXwvIpGzE1RP9LYAXQh5Q/Untitled. + + ## Running the code + + Run `npm i` to install the dependencies. + + Run `npm run dev` to start the development server. + \ No newline at end of file diff --git a/Untitled/default_shadcn_theme.css b/Untitled/default_shadcn_theme.css new file mode 100644 index 0000000..39e1b44 --- /dev/null +++ b/Untitled/default_shadcn_theme.css @@ -0,0 +1,120 @@ +/* KEEP_IN_SYNC(fullscreen/resources/figmake/shadcn/globals.css) */ + +:root { + --font-size: 16px; + --background: #ffffff; + --foreground: oklch(0.145 0 0); + --card: #ffffff; + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: #030213; + --primary-foreground: oklch(1 0 0); + --secondary: oklch(0.95 0.0058 264.53); + --secondary-foreground: #030213; + --muted: #ececf0; + --muted-foreground: #717182; + --accent: #e9ebef; + --accent-foreground: #030213; + --destructive: #d4183d; + --destructive-foreground: #ffffff; + --border: rgba(0, 0, 0, 0.1); + --input: transparent; + --input-background: #f3f3f5; + --switch-background: #cbced4; + --font-weight-medium: 500; + --font-weight-normal: 400; + --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); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: #030213; + --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.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 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.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --font-weight-medium: 500; + --font-weight-normal: 400; + --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(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-input-background: var(--input-background); + --color-switch-background: var(--switch-background); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} diff --git a/Untitled/index.html b/Untitled/index.html new file mode 100644 index 0000000..30e21a2 --- /dev/null +++ b/Untitled/index.html @@ -0,0 +1,16 @@ + + + + + + + Untitled + + + + +
+ + + + \ No newline at end of file diff --git a/Untitled/package.json b/Untitled/package.json new file mode 100644 index 0000000..fad7042 --- /dev/null +++ b/Untitled/package.json @@ -0,0 +1,90 @@ +{ + "name": "@figma/my-make-file", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "dependencies": { + "@emotion/react": "11.14.0", + "@emotion/styled": "11.14.1", + "@mui/icons-material": "7.3.5", + "@mui/material": "7.3.5", + "@popperjs/core": "2.11.8", + "@radix-ui/react-accordion": "1.2.3", + "@radix-ui/react-alert-dialog": "1.1.6", + "@radix-ui/react-aspect-ratio": "1.1.2", + "@radix-ui/react-avatar": "1.1.3", + "@radix-ui/react-checkbox": "1.1.4", + "@radix-ui/react-collapsible": "1.1.3", + "@radix-ui/react-context-menu": "2.2.6", + "@radix-ui/react-dialog": "1.1.6", + "@radix-ui/react-dropdown-menu": "2.1.6", + "@radix-ui/react-hover-card": "1.1.6", + "@radix-ui/react-label": "2.1.2", + "@radix-ui/react-menubar": "1.1.6", + "@radix-ui/react-navigation-menu": "1.2.5", + "@radix-ui/react-popover": "1.1.6", + "@radix-ui/react-progress": "1.1.2", + "@radix-ui/react-radio-group": "1.2.3", + "@radix-ui/react-scroll-area": "1.2.3", + "@radix-ui/react-select": "2.1.6", + "@radix-ui/react-separator": "1.1.2", + "@radix-ui/react-slider": "1.2.3", + "@radix-ui/react-slot": "1.1.2", + "@radix-ui/react-switch": "1.1.3", + "@radix-ui/react-tabs": "1.1.3", + "@radix-ui/react-toggle-group": "1.1.2", + "@radix-ui/react-toggle": "1.1.2", + "@radix-ui/react-tooltip": "1.1.8", + "canvas-confetti": "1.9.4", + "class-variance-authority": "0.7.1", + "clsx": "2.1.1", + "cmdk": "1.1.1", + "date-fns": "3.6.0", + "embla-carousel-react": "8.6.0", + "input-otp": "1.4.2", + "lucide-react": "0.487.0", + "motion": "12.23.24", + "next-themes": "0.4.6", + "react-day-picker": "8.10.1", + "react-dnd": "16.0.1", + "react-dnd-html5-backend": "16.0.1", + "react-hook-form": "7.55.0", + "react-popper": "2.3.0", + "react-resizable-panels": "2.1.7", + "react-responsive-masonry": "2.7.1", + "react-router": "7.13.0", + "react-slick": "0.31.0", + "recharts": "2.15.2", + "sonner": "2.0.3", + "tailwind-merge": "3.2.0", + "tw-animate-css": "1.3.8", + "vaul": "1.1.2" + }, + "devDependencies": { + "@tailwindcss/vite": "4.1.12", + "@vitejs/plugin-react": "4.7.0", + "tailwindcss": "4.1.12", + "vite": "6.3.5" + }, + "peerDependencies": { + "react": "18.3.1", + "react-dom": "18.3.1" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + }, + "pnpm": { + "overrides": { + "vite": "6.3.5" + } + } +} \ No newline at end of file diff --git a/Untitled/pnpm-workspace.yaml b/Untitled/pnpm-workspace.yaml new file mode 100644 index 0000000..e4aab11 --- /dev/null +++ b/Untitled/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - '.' \ No newline at end of file diff --git a/Untitled/postcss.config.mjs b/Untitled/postcss.config.mjs new file mode 100644 index 0000000..531dbec --- /dev/null +++ b/Untitled/postcss.config.mjs @@ -0,0 +1,15 @@ +/** + * PostCSS Configuration + * + * Tailwind CSS v4 (via @tailwindcss/vite) automatically sets up all required + * PostCSS plugins — you do NOT need to include `tailwindcss` or `autoprefixer` here. + * + * This file only exists for adding additional PostCSS plugins, if needed. + * For example: + * + * import postcssNested from 'postcss-nested' + * export default { plugins: [postcssNested()] } + * + * Otherwise, you can leave this file empty. + */ +export default {} diff --git a/Untitled/src/app/App.tsx b/Untitled/src/app/App.tsx new file mode 100644 index 0000000..2040ce9 --- /dev/null +++ b/Untitled/src/app/App.tsx @@ -0,0 +1,6 @@ +import { RouterProvider } from 'react-router'; +import { router } from './routes'; + +export default function App() { + return ; +} \ No newline at end of file diff --git a/Untitled/src/app/components/figma/ImageWithFallback.tsx b/Untitled/src/app/components/figma/ImageWithFallback.tsx new file mode 100644 index 0000000..0e26139 --- /dev/null +++ b/Untitled/src/app/components/figma/ImageWithFallback.tsx @@ -0,0 +1,27 @@ +import React, { useState } from 'react' + +const ERROR_IMG_SRC = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg==' + +export function ImageWithFallback(props: React.ImgHTMLAttributes) { + const [didError, setDidError] = useState(false) + + const handleError = () => { + setDidError(true) + } + + const { src, alt, style, className, ...rest } = props + + return didError ? ( +
+
+ Error loading image +
+
+ ) : ( + {alt} + ) +} diff --git a/Untitled/src/app/components/layout/AccountSidebar.tsx b/Untitled/src/app/components/layout/AccountSidebar.tsx new file mode 100644 index 0000000..4375341 --- /dev/null +++ b/Untitled/src/app/components/layout/AccountSidebar.tsx @@ -0,0 +1,41 @@ +import { Link, useLocation } from 'react-router'; +import { Settings, MapPin, ShoppingBag, MessageSquare } from 'lucide-react'; + +const navItems = [ + { path: '/account/settings', label: 'Настройки', icon: Settings }, + { path: '/account/addresses', label: 'Адреса', icon: MapPin }, + { path: '/account/orders', label: 'Заказы', icon: ShoppingBag }, + { path: '/account/messages', label: 'Сообщения', icon: MessageSquare } +]; + +export function AccountSidebar() { + const location = useLocation(); + + return ( + + ); +} diff --git a/Untitled/src/app/components/layout/AdminSidebar.tsx b/Untitled/src/app/components/layout/AdminSidebar.tsx new file mode 100644 index 0000000..0d6ca85 --- /dev/null +++ b/Untitled/src/app/components/layout/AdminSidebar.tsx @@ -0,0 +1,44 @@ +import { Link, useLocation } from 'react-router'; +import { LayoutDashboard, ShoppingBag, Star, Users, FileText } from 'lucide-react'; + +const navItems = [ + { path: '/admin', label: 'Dashboard', icon: LayoutDashboard }, + { path: '/admin/orders', label: 'Заказы', icon: ShoppingBag }, + { path: '/admin/reviews', label: 'Отзывы', icon: Star }, + { path: '/admin/users', label: 'Пользователи', icon: Users }, + { path: '/admin/info', label: 'Информация', icon: FileText } +]; + +export function AdminSidebar() { + const location = useLocation(); + + return ( + + ); +} diff --git a/Untitled/src/app/components/layout/Header.tsx b/Untitled/src/app/components/layout/Header.tsx new file mode 100644 index 0000000..c725fb0 --- /dev/null +++ b/Untitled/src/app/components/layout/Header.tsx @@ -0,0 +1,54 @@ +import { Link } from 'react-router'; +import { ShoppingCart, User, Menu, Search } from 'lucide-react'; + +interface HeaderProps { + cartCount?: number; +} + +export function Header({ cartCount = 0 }: HeaderProps) { + return ( +
+
+
+
+ +

Craftshop

+ + + +
+ +
+ + + + + + + + + {cartCount > 0 && ( + + {cartCount} + + )} + + + +
+
+
+
+ ); +} diff --git a/Untitled/src/app/components/ui/EmptyState.tsx b/Untitled/src/app/components/ui/EmptyState.tsx new file mode 100644 index 0000000..8f92274 --- /dev/null +++ b/Untitled/src/app/components/ui/EmptyState.tsx @@ -0,0 +1,19 @@ +import { ReactNode } from 'react'; + +interface EmptyStateProps { + icon?: ReactNode; + title: string; + description?: string; + action?: ReactNode; +} + +export function EmptyState({ icon, title, description, action }: EmptyStateProps) { + return ( +
+ {icon &&
{icon}
} +

{title}

+ {description &&

{description}

} + {action &&
{action}
} +
+ ); +} diff --git a/Untitled/src/app/components/ui/accordion.tsx b/Untitled/src/app/components/ui/accordion.tsx new file mode 100644 index 0000000..bd6b1e3 --- /dev/null +++ b/Untitled/src/app/components/ui/accordion.tsx @@ -0,0 +1,66 @@ +"use client"; + +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import { ChevronDownIcon } from "lucide-react"; + +import { cn } from "./utils"; + +function Accordion({ + ...props +}: React.ComponentProps) { + return ; +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + + ); +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ); +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/Untitled/src/app/components/ui/alert-dialog.tsx b/Untitled/src/app/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..875b8df --- /dev/null +++ b/Untitled/src/app/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client"; + +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "./utils"; +import { buttonVariants } from "./button"; + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ); +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/Untitled/src/app/components/ui/alert.tsx b/Untitled/src/app/components/ui/alert.tsx new file mode 100644 index 0000000..9c35976 --- /dev/null +++ b/Untitled/src/app/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "./utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/Untitled/src/app/components/ui/aspect-ratio.tsx b/Untitled/src/app/components/ui/aspect-ratio.tsx new file mode 100644 index 0000000..c16d6bc --- /dev/null +++ b/Untitled/src/app/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +"use client"; + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return ; +} + +export { AspectRatio }; diff --git a/Untitled/src/app/components/ui/avatar.tsx b/Untitled/src/app/components/ui/avatar.tsx new file mode 100644 index 0000000..c990451 --- /dev/null +++ b/Untitled/src/app/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "./utils"; + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/Untitled/src/app/components/ui/badge.tsx b/Untitled/src/app/components/ui/badge.tsx new file mode 100644 index 0000000..60172a5 --- /dev/null +++ b/Untitled/src/app/components/ui/badge.tsx @@ -0,0 +1,23 @@ +import { ReactNode } from 'react'; + +interface BadgeProps { + children: ReactNode; + variant?: 'default' | 'primary' | 'success' | 'warning' | 'destructive'; + className?: string; +} + +export function Badge({ children, variant = 'default', className = '' }: BadgeProps) { + const variantClasses = { + default: 'bg-muted text-foreground', + primary: 'bg-primary text-primary-foreground', + success: 'bg-success text-success-foreground', + warning: 'bg-accent text-accent-foreground', + destructive: 'bg-destructive text-destructive-foreground' + }; + + return ( + + {children} + + ); +} diff --git a/Untitled/src/app/components/ui/breadcrumb.tsx b/Untitled/src/app/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..8f84d7e --- /dev/null +++ b/Untitled/src/app/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { ChevronRight, MoreHorizontal } from "lucide-react"; + +import { cn } from "./utils"; + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return