This commit is contained in:
User
2025-02-15 21:19:04 +03:00
parent 8a1f44692c
commit 73c80dcc16
9 changed files with 488 additions and 116 deletions

View File

@@ -3,10 +3,11 @@
import { useState } from "react" import { useState } from "react"
import { Checkbox } from "./ui/checkbox" import { Checkbox } from "./ui/checkbox"
import { Button } from "./ui/button" import { Button } from "./ui/button"
import { Minus, Plus, Heart, Trash } from 'lucide-react' import { Minus, Plus, Heart, Trash } from "lucide-react"
import Image from "next/image" import Image from "next/image"
import { useCart } from "@/contexts/cart-context" import { useCart } from "@/contexts/cart-context"
import { useFavorites } from "@/contexts/favorites-context" import { useFavorites } from "@/contexts/favorites-context"
import Link from "next/link"
export function CartItems() { export function CartItems() {
const { items, removeFromCart, addToCart, removeAllFromCart, updateQuantity, getTotalQuantity } = useCart() const { items, removeFromCart, addToCart, removeAllFromCart, updateQuantity, getTotalQuantity } = useCart()
@@ -14,14 +15,10 @@ export function CartItems() {
const [selectedItems, setSelectedItems] = useState<number[]>([]) const [selectedItems, setSelectedItems] = useState<number[]>([])
const toggleItem = (id: number) => { const toggleItem = (id: number) => {
setSelectedItems(prev => setSelectedItems((prev) => (prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]))
prev.includes(id)
? prev.filter(item => item !== id)
: [...prev, id]
)
} }
const handleToggleFavorite = (item: typeof items[0]) => { const handleToggleFavorite = (item: (typeof items)[0]) => {
if (isFavorite(item.id)) { if (isFavorite(item.id)) {
removeFromFavorites(item.id) removeFromFavorites(item.id)
} else { } else {
@@ -39,57 +36,58 @@ export function CartItems() {
<span className="font-semibold">Всего товаров: {getTotalQuantity()}</span> <span className="font-semibold">Всего товаров: {getTotalQuantity()}</span>
</div> </div>
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-4">
<Checkbox <Checkbox
checked={selectedItems.length === items.length} checked={selectedItems.length === items.length}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
setSelectedItems(checked ? items.map(item => item.id) : []) setSelectedItems(checked ? items.map((item) => item.id) : [])
}} }}
/> />
<span>Выбрать все</span> <span>Выбрать все</span>
{selectedItems.length > 0 && ( {selectedItems.length > 0 && <button className="text-red-500 ml-4 hover:underline">Удалить выбранные</button>}
<button className="text-red-500 ml-4 hover:underline">
Удалить выбранные
</button>
)}
</div> </div>
{items.map((item) => ( {items.map((item) => (
<div key={item.id} className="flex gap-4 p-4 bg-white rounded-lg"> <div key={item.id} className="flex gap-4 p-4 bg-white rounded-lg">
<Checkbox <Checkbox checked={selectedItems.includes(item.id)} onCheckedChange={() => toggleItem(item.id)} />
checked={selectedItems.includes(item.id)} <Link href={`/product/${item.id}`} className="flex-grow flex gap-4">
onCheckedChange={() => toggleItem(item.id)} <Image
/> src={item.image || "/placeholder.svg"}
<Image alt={item.title}
src={item.image || "/placeholder.svg"} width={100}
alt={item.title} height={100}
width={100} className="object-cover"
height={100} />
className="object-cover" <div className="flex-1">
/> <h3 className="font-medium">{item.title}</h3>
<div className="flex-1"> <div className="flex gap-4 mt-4">
<h3 className="font-medium">{item.title}</h3> <div className="flex items-center gap-2">
<div className="flex gap-4 mt-4"> <Button
<div className="flex items-center gap-2"> variant="outline"
<Button variant="outline" size="icon" onClick={() => handleUpdateQuantity(item.id, item.quantity - 1)}> size="icon"
<Minus className="h-4 w-4" /> onClick={() => handleUpdateQuantity(item.id, item.quantity - 1)}
>
<Minus className="h-4 w-4" />
</Button>
<span className="w-8 text-center">{item.quantity}</span>
<Button
variant="outline"
size="icon"
onClick={() => handleUpdateQuantity(item.id, item.quantity + 1)}
>
<Plus className="h-4 w-4" />
</Button>
</div>
<Button variant="ghost" size="icon" onClick={() => handleToggleFavorite(item)}>
<Heart className={`h-4 w-4 ${isFavorite(item.id) ? "fill-red-500 text-red-500" : ""}`} />
</Button> </Button>
<span className="w-8 text-center">{item.quantity}</span> <Button variant="ghost" size="icon" onClick={() => removeAllFromCart(item.id)}>
<Button variant="outline" size="icon" onClick={() => handleUpdateQuantity(item.id, item.quantity + 1)}> <Trash className="h-4 w-4" />
<Plus className="h-4 w-4" />
</Button> </Button>
</div> </div>
<Button variant="ghost" size="icon" onClick={() => handleToggleFavorite(item)}>
<Heart className={`h-4 w-4 ${isFavorite(item.id) ? 'fill-red-500 text-red-500' : ''}`} />
</Button>
<Button variant="ghost" size="icon" onClick={() => removeAllFromCart(item.id)}>
<Trash className="h-4 w-4" />
</Button>
</div> </div>
</div> </Link>
<div className="text-right"> <div className="text-right">
<div className="text-lg font-bold">{item.price * item.quantity} </div> <div className="text-lg font-bold">{item.price * item.quantity} </div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">{item.price} за шт.</div>
{item.price} за шт.
</div>
</div> </div>
</div> </div>
))} ))}

View File

@@ -0,0 +1,41 @@
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
interface DynamicComponentProps {
initialData?: string
onSubmit?: (data: string) => void
}
export function DynamicComponent({ initialData = "", onSubmit }: DynamicComponentProps) {
const [data, setData] = useState(initialData)
useEffect(() => {
// You can perform any side effects here
console.log("Component mounted or data changed:", data)
}, [data])
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (onSubmit) {
onSubmit(data)
}
}
return (
<div className="p-4 border rounded-lg shadow-sm">
<h2 className="text-lg font-semibold mb-4">Dynamic Component</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<Input type="text" value={data} onChange={(e) => setData(e.target.value)} placeholder="Enter data" />
<Button type="submit">Submit</Button>
</form>
<div className="mt-4">
<p>Current data: {data}</p>
</div>
</div>
)
}

View File

@@ -4,88 +4,135 @@ import { useState } from "react"
import Link from "next/link" import Link from "next/link"
import { Search } from "./search" import { Search } from "./search"
import { Button } from "./ui/button" import { Button } from "./ui/button"
import { ShoppingCart, Heart, Package2, User, Menu } from 'lucide-react' import { ShoppingCart, Heart, User, Menu, X } from "lucide-react"
import { CatalogMenu } from "./catalog-menu" import { CatalogMenu } from "./catalog-menu"
import { Sheet, SheetContent, SheetTrigger } from "./ui/sheet" import { Sheet, SheetContent, SheetTrigger, SheetClose } from "./ui/sheet"
import { useCart } from "@/contexts/cart-context" import { useCart } from "@/contexts/cart-context"
import { useFavorites } from "@/contexts/favorites-context" import { useFavorites } from "@/contexts/favorites-context"
import { useAuth } from "@/contexts/auth-context"
import { Badge } from "./ui/badge" import { Badge } from "./ui/badge"
import { CartSummary } from "./cart-summary"
export function Header() { export function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false) const [isMenuOpen, setIsMenuOpen] = useState(false)
const { getTotalItems } = useCart() const [isCartOpen, setIsCartOpen] = useState(false)
const { getTotalItems, getTotalUniqueItems } = useCart()
const { getTotalFavorites } = useFavorites() const { getTotalFavorites } = useFavorites()
const { isLoggedIn } = useAuth()
return ( return (
<header className="border-b sticky top-0 bg-white z-50"> <header className="border-b sticky top-0 bg-white z-50">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4 py-4 w-[95%]">
<div className="flex flex-col md:flex-row h-auto md:h-16 items-center justify-between"> <div className="flex flex-wrap items-center justify-between">
<div className="flex items-center gap-4 mt-4 md:mt-0"> <div className="flex items-center w-full sm:w-auto mb-4 sm:mb-0 pl-0">
{/* Используем отступ слева для надписи STORE */} <Link href="/" className="text-2xl font-bold text-blue-600 mr-4">
<Link href="/" className="text-2xl font-bold text-blue-600 ml-[100px] md:ml-0"> STORE
ETRNOS
</Link> </Link>
<div className="hidden md:block"> <div className="hidden sm:block">
<CatalogMenu /> <CatalogMenu />
</div> </div>
</div> </div>
<div className="hidden md:block flex-1 max-w-xl mx-4"> <div className="flex items-center w-full sm:flex-1 order-3 sm:order-2 mb-4 sm:mb-0">
<Search /> <div className="flex-grow mr-4">
</div> <Search />
<div className="flex items-center gap-4"> </div>
<div className="hidden md:flex items-center gap-4"> <div className="hidden sm:flex items-center gap-4">
<Button variant="ghost" size="icon" asChild> <Button variant="ghost" size="icon" asChild>
<Link href="/profile"> <Link href={isLoggedIn ? "/profile" : "/login"}>
<User className="h-5 w-5" /> <User className="h-5 w-5" />
</Link> </Link>
</Button> </Button>
<Button variant="ghost" size="icon">
<Package2 className="h-5 w-5" />
</Button>
<Button variant="ghost" size="icon" className="relative" asChild> <Button variant="ghost" size="icon" className="relative" asChild>
<Link href="/favorites"> <Link href="/favorites">
<Heart className="h-5 w-5" /> <Heart className="h-5 w-5" />
{getTotalFavorites() > 0 && ( {getTotalFavorites() > 0 && (
<Badge variant="destructive" className="absolute -top-2 -right-2 h-5 w-5 flex items-center justify-center p-0"> <Badge
variant="destructive"
className="absolute -top-2 -right-2 h-5 w-5 flex items-center justify-center p-0"
>
{getTotalFavorites()} {getTotalFavorites()}
</Badge> </Badge>
)} )}
</Link> </Link>
</Button> </Button>
</div> <Sheet open={isCartOpen} onOpenChange={setIsCartOpen}>
<Button variant="ghost" size="icon" asChild className="relative"> <SheetTrigger asChild>
<Link href="/cart"> <Button variant="ghost" size="icon" className="relative">
<ShoppingCart className="h-5 w-5" /> <ShoppingCart className="h-5 w-5" />
{getTotalItems() > 0 && ( {getTotalUniqueItems() > 0 && (
<Badge variant="destructive" className="absolute -top-2 -right-2 h-5 w-5 flex items-center justify-center p-0"> <Badge
{getTotalItems()} variant="destructive"
</Badge> className="absolute -top-2 -right-2 h-5 w-5 flex items-center justify-center p-0"
)} >
</Link> {getTotalUniqueItems()}
</Button> </Badge>
<Sheet open={isMenuOpen} onOpenChange={setIsMenuOpen}> )}
<SheetTrigger asChild> </Button>
<Button variant="ghost" size="icon" className="md:hidden"> </SheetTrigger>
<Menu className="h-5 w-5" /> <SheetContent side="right" className="w-full sm:max-w-md">
</Button> <div className="flex justify-between items-center mb-6">
</SheetTrigger> <h2 className="text-2xl font-bold">Корзина</h2>
<SheetContent side="left" className="w-[300px] sm:w-[400px]"> <SheetClose asChild>
<nav className="flex flex-col gap-4"> <Button variant="ghost" size="icon">
<Link href="/" onClick={() => setIsMenuOpen(false)}>Главная</Link> <X className="h-5 w-5" />
<Link href="/profile" onClick={() => setIsMenuOpen(false)}>Профиль</Link> </Button>
<Link href="/cart" onClick={() => setIsMenuOpen(false)}>Корзина</Link> </SheetClose>
<div className="md:hidden">
<CatalogMenu />
</div> </div>
</nav> <CartSummary />
</SheetContent> <div className="mt-6">
</Sheet> <Button asChild className="w-full">
<Link href="/cart" onClick={() => setIsCartOpen(false)}>
Перейти в корзину
</Link>
</Button>
</div>
</SheetContent>
</Sheet>
</div>
<div className="sm:hidden">
<Sheet open={isMenuOpen} onOpenChange={setIsMenuOpen}>
<SheetTrigger asChild>
<Button variant="ghost" size="icon">
<Menu className="h-5 w-5" />
</Button>
</SheetTrigger>
<SheetContent side="right" className="w-full sm:max-w-md">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">Меню</h2>
<SheetClose asChild>
<Button variant="ghost" size="icon">
<X className="h-5 w-5" />
</Button>
</SheetClose>
</div>
<nav className="flex flex-col gap-4">
<CatalogMenu />
<Link
href={isLoggedIn ? "/profile" : "/login"}
className="flex items-center gap-2 p-2 hover:bg-gray-100 rounded-md"
>
<User className="h-5 w-5" />
<span>Личный кабинет</span>
</Link>
<Link href="/favorites" className="flex items-center gap-2 p-2 hover:bg-gray-100 rounded-md">
<Heart className="h-5 w-5" />
<span>Избранное</span>
{getTotalFavorites() > 0 && <Badge variant="destructive">{getTotalFavorites()}</Badge>}
</Link>
<Link href="/cart" className="flex items-center gap-2 p-2 hover:bg-gray-100 rounded-md">
<ShoppingCart className="h-5 w-5" />
<span>Корзина</span>
{getTotalUniqueItems() > 0 && <Badge variant="destructive">{getTotalUniqueItems()}</Badge>}
</Link>
</nav>
</SheetContent>
</Sheet>
</div>
</div> </div>
</div> </div>
<div className="md:hidden mt-2 mb-4">
<Search />
</div>
</div> </div>
</header> </header>
) )
} }

View File

@@ -0,0 +1,68 @@
"use client"
import { useState } from "react"
import Image from "next/image"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "./ui/button"
interface ImageGalleryProps {
images: string[]
}
export function ImageGallery({ images }: ImageGalleryProps) {
// Check if images is undefined or not an array
if (!images || !Array.isArray(images)) {
console.error('Images prop is undefined or not an array:', images);
return <div>Error: No images provided</div>;
}
const [currentIndex, setCurrentIndex] = useState(0)
const goToPrevious = () => {
setCurrentIndex((prevIndex) => (prevIndex === 0 ? images.length - 1 : prevIndex - 1))
}
const goToNext = () => {
setCurrentIndex((prevIndex) => (prevIndex === images.length - 1 ? 0 : prevIndex + 1))
}
return (
<div className="relative aspect-square">
<div className="absolute inset-0 overflow-hidden rounded-lg">
<Image
src={images[currentIndex] || "/placeholder.svg"}
alt={`Product image ${currentIndex + 1}`}
width={500}
height={500}
className="absolute inset-0 w-full h-full object-cover"
priority={currentIndex === 0}
/>
</div>
<Button
variant="outline"
size="icon"
className="absolute left-2 top-1/2 transform -translate-y-1/2"
onClick={goToPrevious}
>
<ChevronLeft className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="icon"
className="absolute right-2 top-1/2 transform -translate-y-1/2"
onClick={goToNext}
>
<ChevronRight className="h-4 w-4" />
</Button>
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex justify-center space-x-2">
{images.map((_, index) => (
<button
key={index}
className={`h-2 w-2 rounded-full ${index === currentIndex ? "bg-blue-600" : "bg-gray-300"}`}
onClick={() => setCurrentIndex(index)}
/>
))}
</div>
</div>
)
}

View File

@@ -4,10 +4,11 @@ import { useState } from "react"
import { Button } from "./ui/button" import { Button } from "./ui/button"
import { Textarea } from "./ui/textarea" import { Textarea } from "./ui/textarea"
import { Label } from "./ui/label" import { Label } from "./ui/label"
import { Star } from 'lucide-react' import { LogIn, Star } from 'lucide-react'
import { useAuth } from "@/contexts/auth-context" import { useAuth } from "@/contexts/auth-context"
import Link from "next/link" import Link from "next/link"
interface ReviewFormProps { interface ReviewFormProps {
productId: number productId: number
} }
@@ -16,19 +17,44 @@ export function ReviewForm({ productId }: ReviewFormProps) {
const [rating, setRating] = useState(0) const [rating, setRating] = useState(0)
const [comment, setComment] = useState("") const [comment, setComment] = useState("")
const { isLoggedIn } = useAuth() const { isLoggedIn } = useAuth()
const {email} = useAuth();
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
// Here you would typically send the review data to your backend
console.log("Submitting review:", { productId, rating, comment }) const reviewData = {
// Reset form after submission username: {email}=useAuth(), // Здесь можно подтянуть имя из Auth-контекста
setRating(0) rating,
setComment("") comment,
}
try {
const response = await fetch(`http://localhost:8080/product/1`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(reviewData),
})
if (!response.ok) {
throw new Error("Ошибка при отправке отзыва")
}
console.log("Отзыв успешно отправлен!")
setRating(0)
setComment("")
} catch (error) {
console.error("Ошибка:", error)
}
} }
if (!isLoggedIn) { if (!isLoggedIn) {
return ( return (
<p className="text-gray-600">Чтобы оставить отзыв, пожалуйста, <Link href="/login" className="text-blue-600 hover:underline">войдите в систему</Link>.</p> <p className="text-gray-600">
Чтобы оставить отзыв, пожалуйста, <Link href="/login" className="text-blue-600 hover:underline">войдите в систему</Link>.
</p>
) )
} }
@@ -69,4 +95,3 @@ export function ReviewForm({ productId }: ReviewFormProps) {
</form> </form>
) )
} }

View File

@@ -1,18 +1,53 @@
import { Review } from "@/types/product" import { useEffect, useState } from "react"
import { Star } from 'lucide-react' import type { Review } from "@/types/product"
import { Star } from "lucide-react"
import { useAuth } from "@/contexts/auth-context" import { useAuth } from "@/contexts/auth-context"
import Link from "next/link" import Link from "next/link"
import { Button } from "./ui/button"
interface ReviewListProps { interface ReviewListProps {
reviews: Review[] productId: number
} }
export function ReviewList({ reviews }: ReviewListProps) { export function ReviewList({ productId }: ReviewListProps) {
const { isLoggedIn } = useAuth() const { isLoggedIn } = useAuth()
const [reviews, setReviews] = useState<Review[]>([])
useEffect(() => {
const fetchReviews = async () => {
try {
const response = await fetch(`http://localhost:8080/product/1`)
if (!response.ok) throw new Error("Ошибка загрузки отзывов")
const data = await response.json()
console.log("Загруженные отзывы:", data) // Check the data received
setReviews(data)
} catch (error) {
console.error(error)
}
}
fetchReviews()
}, [productId])
interface Review {
id: number;
product_id: number;
username: string;
rating: number;
comment: string;
createdAt: string;
}
if (!isLoggedIn) { if (!isLoggedIn) {
return ( return (
<p className="text-gray-600">Чтобы просматривать отзывы, пожалуйста, <Link href="/login" className="text-blue-600 hover:underline">войдите в систему</Link>.</p> <div className="text-center py-8">
<p className="text-gray-600 mb-4">Чтобы просматривать отзывы, пожалуйста, войдите в систему.</p>
<Button asChild variant="outline" className="rounded-full">
<Link href="/login">Войти</Link>
</Button>
</div>
) )
} }
@@ -25,25 +60,24 @@ export function ReviewList({ reviews }: ReviewListProps) {
reviews.map((review) => ( reviews.map((review) => (
<div key={review.id} className="border-b pb-4"> <div key={review.id} className="border-b pb-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* Вывод рейтинга с помощью звезд */}
<div className="flex"> <div className="flex">
{[1, 2, 3, 4, 5].map((star) => ( {[1, 2, 3, 4, 5].map((star) => (
<Star <Star
key={star} key={star}
className={`h-5 w-5 ${ className={`h-5 w-5 ${star <= review.rating ? "text-yellow-400 fill-yellow-400" : "text-gray-300"}`}
star <= review.rating ? 'text-yellow-400 fill-yellow-400' : 'text-gray-300'
}`}
/> />
))} ))}
</div> </div>
<span className="text-sm text-gray-500"> {/* Дата отзыва */}
{new Date(review.createdAt).toLocaleDateString()} <span className="text-sm text-gray-500">{new Date(review.createdAt).toLocaleDateString()}</span>
</span>
</div> </div>
{/* Комментарий */}
<p className="mt-2">{review.comment}</p> <p className="mt-2">{review.comment}</p>
</div> </div>
)) ))
)} )}
</div> </div>
) )
} }

Binary file not shown.

View File

@@ -0,0 +1,143 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/cors"
)
type Review struct {
ID int `json:"id"`
ProductID int `json:"product_id"`
Username string `json:"username"`
Rating int `json:"rating"`
Comment string `json:"comment"`
CreatedAt string `json:"createdAt"`
}
var db *sql.DB
func main() {
var err error
db, err = sql.Open("sqlite3", "./reviews.db")
if err != nil {
log.Fatal("Ошибка открытия БД:", err)
}
createTable()
r := mux.NewRouter()
r.HandleFunc("/product/{product_id:[0-9]+}", getReviews).Methods("GET")
r.HandleFunc("/product/{product_id:[0-9]+}", addReview).Methods("POST")
corsHandler := cors.New(cors.Options{
AllowedOrigins: []string{"http://localhost:3000"},
AllowedMethods: []string{"GET", "POST"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
}).Handler(r)
log.Println("Server is running on port 8080...")
if err := http.ListenAndServe(":8080", corsHandler); err != nil {
log.Fatal("Ошибка запуска сервера:", err)
}
}
func createTable() {
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS reviews (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
username TEXT NOT NULL,
rating INTEGER NOT NULL CHECK(rating >= 1 AND rating <= 5),
comment TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`)
if err != nil {
log.Fatal("Ошибка создания таблицы:", err)
}
}
func getReviews(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
productID := vars["product_id"]
rows, err := db.Query("SELECT id, product_id, username, rating, comment, created_at FROM reviews WHERE product_id = ?", productID)
if err != nil {
http.Error(w, "Ошибка при получении отзывов", http.StatusInternalServerError)
log.Println("Ошибка при запросе отзывов:", err)
return
}
defer rows.Close()
var reviews []Review
for rows.Next() {
var review Review
if err := rows.Scan(&review.ID, &review.ProductID, &review.Username, &review.Rating, &review.Comment, &review.CreatedAt); err != nil {
http.Error(w, "Ошибка при обработке данных", http.StatusInternalServerError)
log.Println("Ошибка при сканировании строк:", err)
return
}
reviews = append(reviews, review)
}
// Log the reviews being returned
log.Printf("Reviews fetched: %+v", reviews)
if len(reviews) == 0 {
http.Error(w, "Нет отзывов", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(reviews)
}
func addReview(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
productID, err := strconv.Atoi(vars["product_id"])
if err != nil {
http.Error(w, "Некорректный product_id", http.StatusBadRequest)
log.Println("Ошибка преобразования product_id:", err)
return
}
var review Review
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
http.Error(w, "Некорректный JSON", http.StatusBadRequest)
log.Println("Ошибка декодирования JSON:", err)
return
}
review.ProductID = productID
log.Printf("Попытка добавить отзыв: %+v", review)
stmt, err := db.Prepare("INSERT INTO reviews (product_id, username, rating, comment, created_at) VALUES (?, ?, ?, ?, datetime('now'))")
if err != nil {
http.Error(w, "Ошибка при подготовке SQL-запроса", http.StatusInternalServerError)
log.Println("Ошибка при подготовке запроса:", err)
return
}
defer stmt.Close()
result, err := stmt.Exec(review.ProductID, review.Username, review.Rating, review.Comment)
if err != nil {
http.Error(w, "Ошибка при добавлении отзыва", http.StatusInternalServerError)
log.Println("Ошибка при выполнении SQL-запроса:", err)
return
}
rowsAffected, _ := result.RowsAffected()
log.Println("Добавлено строк:", rowsAffected)
w.WriteHeader(http.StatusCreated)
fmt.Fprintln(w, "Отзыв успешно добавлен")
}

View File

@@ -0,0 +1,16 @@
"use client"
interface VideoPlayerProps {
videoUrl: string
}
export function VideoPlayer({ videoUrl }: VideoPlayerProps) {
return (
<div className="relative aspect-square rounded-lg overflow-hidden">
<video src={videoUrl} controls className="absolute inset-0 w-full h-full object-cover">
Your browser does not support the video tag.
</video>
</div>
)
}