-
+
- Eternos
+ STORE
-
+
+
+
+
+
-
-
-
+
+
+
)
diff --git a/frontend/style/components/product-card.tsx b/frontend/style/components/product-card.tsx
index 9c33d2c5..17cfea73 100644
--- a/frontend/style/components/product-card.tsx
+++ b/frontend/style/components/product-card.tsx
@@ -1,24 +1,54 @@
+"use client"
+
import Image from "next/image"
-import { Heart } from 'lucide-react'
+import Link from "next/link"
+import { Heart, ShoppingCart } from 'lucide-react'
import { Button } from "./ui/button"
-import { Badge } from "./ui/badge"
+import { useCart } from "@/contexts/cart-context"
+import { useFavorites } from "@/contexts/favorites-context"
interface ProductCardProps {
product: {
id: number
title: string
price: number
- oldPrice: number
- discount: number
image: string
- isHotDeal?: boolean
- isSale?: boolean
}
}
export function ProductCard({ product }: ProductCardProps) {
+ const { addToCart, removeFromCart } = useCart()
+ const { addToFavorites, removeFromFavorites, isFavorite } = useFavorites()
+
+ const handleAddToCart = (e: React.MouseEvent) => {
+ e.preventDefault()
+ addToCart({
+ id: product.id,
+ title: product.title,
+ price: product.price,
+ })
+ }
+
+ const handleRemoveFromCart = (e: React.MouseEvent) => {
+ e.preventDefault()
+ removeFromCart(product.id)
+ }
+
+ const handleToggleFavorite = (e: React.MouseEvent) => {
+ e.preventDefault()
+ if (isFavorite(product.id)) {
+ removeFromFavorites(product.id)
+ } else {
+ addToFavorites({
+ id: product.id,
+ title: product.title,
+ price: product.price,
+ })
+ }
+ }
+
return (
-
+
-
+
- {product.isHotDeal && (
-
- НАРАСХВАТ
-
- )}
-
- {product.price} ₽
-
- {product.oldPrice} ₽
-
- -{product.discount}%
-
+
{product.price} ₽
{product.title}
- {product.isSale && (
-
- Распродажа
-
- )}
+
+
+ В корзину
+
+
+ -
+
+
-
+
)
}
diff --git a/frontend/style/components/product-detail.tsx b/frontend/style/components/product-detail.tsx
new file mode 100644
index 00000000..4810db5f
--- /dev/null
+++ b/frontend/style/components/product-detail.tsx
@@ -0,0 +1,97 @@
+"use client"
+
+import Image from "next/image"
+import { useState } from "react"
+import { Heart, ShoppingCart, Minus, Plus } from 'lucide-react'
+import { Button } from "./ui/button"
+import { useCart } from "@/contexts/cart-context"
+import { useFavorites } from "@/contexts/favorites-context"
+import { Product } from "@/types/product"
+
+interface ProductDetailProps {
+ product: Product
+}
+
+export function ProductDetail({ product }: ProductDetailProps) {
+ const [quantity, setQuantity] = useState(1)
+ const { addToCart } = useCart()
+ const { addToFavorites, removeFromFavorites, isFavorite } = useFavorites()
+
+ const handleAddToCart = () => {
+ addToCart({
+ id: product.id,
+ title: product.title,
+ price: product.price,
+ quantity: quantity
+ })
+ }
+
+ const handleToggleFavorite = () => {
+ if (isFavorite(product.id)) {
+ removeFromFavorites(product.id)
+ } else {
+ addToFavorites({
+ id: product.id,
+ title: product.title,
+ price: product.price,
+ })
+ }
+ }
+
+ return (
+
+
+
+
+
+
{product.title}
+ {product.title.startsWith('[Draft]') && (
+
+ Draft Version
+
+ )}
+
+ {product.price} ₽
+
+
+
+
setQuantity(prev => Math.max(1, prev - 1))}
+ >
+
+
+
{quantity}
+
setQuantity(prev => prev + 1)}
+ >
+
+
+
+
+ Добавить в корзину
+
+
+
+
+
+
+
Описание
+
+ Подробное описание товара. Здесь может быть длинный текст с характеристиками и особенностями продукта.
+
+
+
+
+ )
+}
+
diff --git a/frontend/style/components/product-grid.tsx b/frontend/style/components/product-grid.tsx
index 30ea6772..242dee59 100644
--- a/frontend/style/components/product-grid.tsx
+++ b/frontend/style/components/product-grid.tsx
@@ -1,67 +1,14 @@
import { ProductCard } from "./product-card"
+import { Product } from "@/types/product"
-const SAMPLE_PRODUCTS = [
- {
- id: 1,
- title: "Кофе растворимый Жокей Крепкий, 3 в 1, с сахаром",
- price: 89,
- oldPrice: 172,
- discount: 48,
- image: "/placeholder.svg",
- isHotDeal: true,
- isSale: true,
- },
- {
- id: 2,
- title: "Рексона Дезодорант женский твердый стик",
- price: 235,
- oldPrice: 397,
- discount: 40,
- image: "/placeholder.svg",
- isHotDeal: true,
- },
- {
- id: 3,
- title: "Сухой корм Мираторг MEAT с нежной телятиной",
- price: 187,
- oldPrice: 294,
- discount: 36,
- image: "/placeholder.svg",
- isHotDeal: true,
- },
- {
- id: 4,
- title: "Сухой корм KITEKAT™ для взрослых кошек «Мясной пир»",
- price: 174,
- oldPrice: 209,
- discount: 16,
- image: "/placeholder.svg",
- isHotDeal: true,
- },
- {
- id: 5,
- title: "Специальное чистящее средство для стиральных машин",
- price: 197,
- oldPrice: 469,
- discount: 57,
- image: "/placeholder.svg",
- isHotDeal: true,
- },
- {
- id: 6,
- title: "Крем для лица увлажняющий",
- price: 184,
- oldPrice: 413,
- discount: 55,
- image: "/placeholder.svg",
- isHotDeal: true,
- },
-]
+interface ProductGridProps {
+ products: Product[]
+}
-export function ProductGrid() {
+export function ProductGrid({ products }: ProductGridProps) {
return (
-
- {SAMPLE_PRODUCTS.map((product) => (
+
+ {products.map((product) => (
))}
diff --git a/frontend/style/components/register-form.tsx b/frontend/style/components/register-form.tsx
new file mode 100644
index 00000000..54716e00
--- /dev/null
+++ b/frontend/style/components/register-form.tsx
@@ -0,0 +1,71 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import Link from "next/link"
+
+export function RegisterForm() {
+ const [formData, setFormData] = useState({
+ email: "",
+ password: "",
+ confirmPassword: "",
+ })
+
+ const handleChange = (e: React.ChangeEvent
) => {
+ const { name, value } = e.target
+ setFormData(prev => ({ ...prev, [name]: value }))
+ }
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault()
+ // Here you would typically send the data to your backend
+ console.log("Form submitted:", formData)
+ }
+
+ return (
+
+ )
+}
+
diff --git a/frontend/style/components/search.tsx b/frontend/style/components/search.tsx
index 5082976e..e953a231 100644
--- a/frontend/style/components/search.tsx
+++ b/frontend/style/components/search.tsx
@@ -1,17 +1,36 @@
"use client"
+import { useState } from 'react'
+import { useRouter } from 'next/navigation'
import { SearchIcon } from 'lucide-react'
import { Input } from "./ui/input"
+import { Button } from "./ui/button"
export function Search() {
+ const [searchTerm, setSearchTerm] = useState('')
+ const router = useRouter()
+
+ const handleSearch = (e: React.FormEvent) => {
+ e.preventDefault()
+ if (searchTerm.trim()) {
+ router.push(`/search?q=${encodeURIComponent(searchTerm.trim())}`)
+ }
+ }
+
return (
-
-
+
+
+
+ Найти
+
+
)
}
diff --git a/frontend/style/components/ui/label.tsx b/frontend/style/components/ui/label.tsx
new file mode 100644
index 00000000..53418217
--- /dev/null
+++ b/frontend/style/components/ui/label.tsx
@@ -0,0 +1,26 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/frontend/style/components/ui/sheet.tsx b/frontend/style/components/ui/sheet.tsx
new file mode 100644
index 00000000..a26d7b37
--- /dev/null
+++ b/frontend/style/components/ui/sheet.tsx
@@ -0,0 +1,7 @@
+import { type ClassValue, clsx } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
+
diff --git a/frontend/style/components/user-profile.tsx b/frontend/style/components/user-profile.tsx
new file mode 100644
index 00000000..8c31d40e
--- /dev/null
+++ b/frontend/style/components/user-profile.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import { useState } from "react"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import Link from "next/link"
+
+export function UserProfile() {
+ const [isLoggedIn, setIsLoggedIn] = useState(false)
+
+ if (!isLoggedIn) {
+ return (
+
+
+ Войти
+
+
+ Зарегистрироваться
+
+
+ )
+ }
+
+ return (
+
+
Добро пожаловать, Иван Иванов!
+ setIsLoggedIn(false)} className="w-full">Выйти
+
+ )
+}
+
diff --git a/frontend/style/contexts/auth-context.tsx b/frontend/style/contexts/auth-context.tsx
new file mode 100644
index 00000000..dd00ecbd
--- /dev/null
+++ b/frontend/style/contexts/auth-context.tsx
@@ -0,0 +1,33 @@
+"use client"
+
+import React, { createContext, useContext, useState } from 'react'
+
+type AuthContextType = {
+ isLoggedIn: boolean
+ login: () => void
+ logout: () => void
+}
+
+const AuthContext = createContext(undefined)
+
+export const useAuth = () => {
+ const context = useContext(AuthContext)
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider')
+ }
+ return context
+}
+
+export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [isLoggedIn, setIsLoggedIn] = useState(false)
+
+ const login = () => setIsLoggedIn(true)
+ const logout = () => setIsLoggedIn(false)
+
+ return (
+
+ {children}
+
+ )
+}
+
diff --git a/frontend/style/contexts/cart-context.tsx b/frontend/style/contexts/cart-context.tsx
new file mode 100644
index 00000000..9cc1387a
--- /dev/null
+++ b/frontend/style/contexts/cart-context.tsx
@@ -0,0 +1,99 @@
+"use client"
+
+import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'
+import { CartItem, saveCart, getCart, clearCart } from '@/lib/cartStorage'
+
+type CartContextType = {
+ items: CartItem[]
+ addToCart: (item: Omit) => void
+ removeFromCart: (id: number) => void
+ removeAllFromCart: (id: number) => void
+ updateQuantity: (id: number, quantity: number) => void
+ clearCart: () => void
+ getTotalItems: () => number
+}
+
+const CartContext = createContext(undefined)
+
+export const useCart = () => {
+ const context = useContext(CartContext)
+ if (!context) {
+ throw new Error('useCart must be used within a CartProvider')
+ }
+ return context
+}
+
+export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [items, setItems] = useState([])
+
+ useEffect(() => {
+ const savedCart = getCart()
+ if (savedCart.length > 0) {
+ setItems(savedCart)
+ }
+ }, [])
+
+ useEffect(() => {
+ saveCart(items)
+ }, [items])
+
+ const addToCart = useCallback((newItem: Omit) => {
+ setItems(currentItems => {
+ const existingItem = currentItems.find(item => item.id === newItem.id)
+ if (existingItem) {
+ return currentItems.map(item =>
+ item.id === newItem.id ? { ...item, quantity: item.quantity + 1 } : item
+ )
+ }
+ return [...currentItems, { ...newItem, quantity: 1 }]
+ })
+ }, [])
+
+ const removeFromCart = useCallback((id: number) => {
+ setItems(currentItems => {
+ const existingItem = currentItems.find(item => item.id === id)
+ if (existingItem && existingItem.quantity > 1) {
+ return currentItems.map(item =>
+ item.id === id ? { ...item, quantity: item.quantity - 1 } : item
+ )
+ }
+ return currentItems.filter(item => item.id !== id)
+ })
+ }, [])
+
+ const removeAllFromCart = useCallback((id: number) => {
+ setItems(currentItems => currentItems.filter(item => item.id !== id))
+ }, [])
+
+ const updateQuantity = useCallback((id: number, quantity: number) => {
+ setItems(currentItems =>
+ currentItems.map(item =>
+ item.id === id ? { ...item, quantity: Math.max(1, quantity) } : item
+ )
+ )
+ }, [])
+
+ const clearCartItems = useCallback(() => {
+ setItems([])
+ clearCart()
+ }, [])
+
+ const getTotalItems = useCallback(() => {
+ return items.reduce((total, item) => total + item.quantity, 0)
+ }, [items])
+
+ return (
+
+ {children}
+
+ )
+}
+
diff --git a/frontend/style/contexts/favorites-context.tsx b/frontend/style/contexts/favorites-context.tsx
new file mode 100644
index 00000000..3a3dceee
--- /dev/null
+++ b/frontend/style/contexts/favorites-context.tsx
@@ -0,0 +1,59 @@
+"use client"
+
+import React, { createContext, useContext, useState, useCallback } from 'react'
+
+type FavoriteItem = {
+ id: number
+ title: string
+ price: number
+}
+
+type FavoritesContextType = {
+ items: FavoriteItem[]
+ addToFavorites: (item: FavoriteItem) => void
+ removeFromFavorites: (id: number) => void
+ isFavorite: (id: number) => boolean
+ getTotalFavorites: () => number
+}
+
+const FavoritesContext = createContext(undefined)
+
+export const useFavorites = () => {
+ const context = useContext(FavoritesContext)
+ if (!context) {
+ throw new Error('useFavorites must be used within a FavoritesProvider')
+ }
+ return context
+}
+
+export const FavoritesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [items, setItems] = useState([])
+
+ const addToFavorites = useCallback((newItem: FavoriteItem) => {
+ setItems(currentItems => {
+ if (!currentItems.some(item => item.id === newItem.id)) {
+ return [...currentItems, newItem]
+ }
+ return currentItems
+ })
+ }, [])
+
+ const removeFromFavorites = useCallback((id: number) => {
+ setItems(currentItems => currentItems.filter(item => item.id !== id))
+ }, [])
+
+ const isFavorite = useCallback((id: number) => {
+ return items.some(item => item.id === id)
+ }, [items])
+
+ const getTotalFavorites = useCallback(() => {
+ return items.length
+ }, [items])
+
+ return (
+
+ {children}
+
+ )
+}
+
diff --git a/frontend/style/lib/cartStorage.ts b/frontend/style/lib/cartStorage.ts
new file mode 100644
index 00000000..e8523f00
--- /dev/null
+++ b/frontend/style/lib/cartStorage.ts
@@ -0,0 +1,39 @@
+import fs from 'fs'
+import path from 'path'
+
+const DATA_DIR = path.join(process.cwd(), 'data')
+const CART_FILE_PATH = path.join(DATA_DIR, 'cart.json')
+
+export interface CartItem {
+ id: number
+ title: string
+ price: number
+ quantity: number
+}
+
+const ensureDataDir = () => {
+ if (!fs.existsSync(DATA_DIR)) {
+ fs.mkdirSync(DATA_DIR, { recursive: true })
+ }
+}
+
+export const saveCart = (cartItems: CartItem[]): void => {
+ ensureDataDir()
+ const cartData = JSON.stringify(cartItems, null, 2)
+ fs.writeFileSync(CART_FILE_PATH, cartData)
+}
+
+export const getCart = (): CartItem[] => {
+ ensureDataDir()
+ if (!fs.existsSync(CART_FILE_PATH)) {
+ return []
+ }
+ const cartData = fs.readFileSync(CART_FILE_PATH, 'utf-8')
+ return JSON.parse(cartData)
+}
+
+export const clearCart = (): void => {
+ ensureDataDir()
+ fs.writeFileSync(CART_FILE_PATH, '[]')
+}
+
diff --git a/frontend/style/lib/cookieManager.js b/frontend/style/lib/cookieManager.js
new file mode 100644
index 00000000..4bf00ca3
--- /dev/null
+++ b/frontend/style/lib/cookieManager.js
@@ -0,0 +1,16 @@
+import Cookies from 'js-cookie';
+
+export const updateCartCookie = (cartItems) => {
+ const cartData = JSON.stringify(cartItems);
+ Cookies.set('cart', cartData, { expires: 7 }); // Cookie expires in 7 days
+};
+
+export const getCartFromCookie = () => {
+ const cartData = Cookies.get('cart');
+ return cartData ? JSON.parse(cartData) : [];
+};
+
+export const clearCartCookie = () => {
+ Cookies.remove('cart');
+};
+
diff --git a/frontend/style/lib/sample-products.ts b/frontend/style/lib/sample-products.ts
new file mode 100644
index 00000000..fe664277
--- /dev/null
+++ b/frontend/style/lib/sample-products.ts
@@ -0,0 +1,60 @@
+import { Product } from "@/types/product"
+
+export const SAMPLE_PRODUCTS: Product[] = [
+ {
+ id: 1,
+ title: "Кофе растворимый Жокей Крепкий, 3 в 1, с сахаром",
+ price: 89,
+ oldPrice: 172,
+ discount: 48,
+ image: "/placeholder.svg",
+ isHotDeal: true,
+ isSale: true,
+ },
+ {
+ id: 2,
+ title: "Рексона Дезодорант женский твердый стик",
+ price: 235,
+ oldPrice: 397,
+ discount: 40,
+ image: "/placeholder.svg",
+ isHotDeal: true,
+ },
+ {
+ id: 3,
+ title: "Сухой корм Мираторг MEAT с нежной телятиной",
+ price: 187,
+ oldPrice: 294,
+ discount: 36,
+ image: "/placeholder.svg",
+ isHotDeal: true,
+ },
+ {
+ id: 4,
+ title: "Сухой корм KITEKAT™ для взрослых кошек «Мясной пир»",
+ price: 174,
+ oldPrice: 209,
+ discount: 16,
+ image: "/placeholder.svg",
+ isHotDeal: true,
+ },
+ {
+ id: 5,
+ title: "Специальное чистящее средство для стиральных машин",
+ price: 197,
+ oldPrice: 469,
+ discount: 57,
+ image: "/placeholder.svg",
+ isHotDeal: true,
+ },
+ {
+ id: 6,
+ title: "Крем для лица увлажняющий",
+ price: 184,
+ oldPrice: 413,
+ discount: 55,
+ image: "/placeholder.svg",
+ isHotDeal: true,
+ },
+]
+
diff --git a/frontend/style/lib/utils.ts b/frontend/style/lib/utils.ts
index bd0c391d..a26d7b37 100644
--- a/frontend/style/lib/utils.ts
+++ b/frontend/style/lib/utils.ts
@@ -1,6 +1,7 @@
-import { clsx, type ClassValue } from "clsx"
+import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
-
+
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+
diff --git a/frontend/style/package-lock.json b/frontend/style/package-lock.json
index 607b9a12..2a9e385e 100644
--- a/frontend/style/package-lock.json
+++ b/frontend/style/package-lock.json
@@ -13,14 +13,20 @@
"@nextui-org/theme": "^2.4.5",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.4",
+ "@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^11.16.0",
+ "fs": "^0.0.1-security",
+ "fs-react": "^0.0.4",
+ "js-cookie": "^3.0.5",
"lucide-react": "^0.469.0",
"next": "14.2.16",
+ "path": "^0.12.7",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.6.0",
@@ -890,6 +896,42 @@
}
}
},
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz",
+ "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.1",
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-context": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.3",
+ "@radix-ui/react-focus-guards": "1.1.1",
+ "@radix-ui/react-focus-scope": "1.1.1",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-portal": "1.1.3",
+ "@radix-ui/react-presence": "1.1.2",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-slot": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "^2.6.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-direction": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
@@ -932,6 +974,46 @@
}
}
},
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
+ "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
+ "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.1",
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-id": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
@@ -950,6 +1032,29 @@
}
}
},
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz",
+ "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.3.tgz",
@@ -986,6 +1091,30 @@
}
}
},
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
+ "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.1",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-presence": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
@@ -1871,6 +2000,18 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -2535,6 +2676,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -3418,6 +3565,18 @@
}
}
},
+ "node_modules/fs": {
+ "version": "0.0.1-security",
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==",
+ "license": "ISC"
+ },
+ "node_modules/fs-react": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/fs-react/-/fs-react-0.0.4.tgz",
+ "integrity": "sha512-F7Yy/gDd3ewTusPCnqwT2C/qGsC1aDHq4uekrEjeFQdDfztqenSP1gAVUFpju41Tvnh+D/pTLsNGkdRP5teY7g==",
+ "license": "ISC"
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -3504,6 +3663,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -4320,6 +4488,15 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/js-cookie": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
+ "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -4931,6 +5108,16 @@
"node": ">=6"
}
},
+ "node_modules/path": {
+ "version": "0.12.7",
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
+ "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "process": "^0.11.1",
+ "util": "^0.10.3"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -5181,6 +5368,15 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -5255,6 +5451,75 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz",
+ "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -6320,12 +6585,70 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "2.0.3"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/util/node_modules/inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
+ "license": "ISC"
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/frontend/style/package.json b/frontend/style/package.json
index 08f7b187..dfbe873a 100644
--- a/frontend/style/package.json
+++ b/frontend/style/package.json
@@ -14,14 +14,20 @@
"@nextui-org/theme": "^2.4.5",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.4",
+ "@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-navigation-menu": "^1.2.3",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^11.16.0",
+ "fs": "^0.0.1-security",
+ "fs-react": "^0.0.4",
+ "js-cookie": "^3.0.5",
"lucide-react": "^0.469.0",
"next": "14.2.16",
+ "path": "^0.12.7",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.6.0",
diff --git a/frontend/style/types/product.ts b/frontend/style/types/product.ts
new file mode 100644
index 00000000..8bb7d93f
--- /dev/null
+++ b/frontend/style/types/product.ts
@@ -0,0 +1,7 @@
+export interface Product {
+ id: number
+ title: string
+ price: number
+ image: string
+}
+