From 73c80dcc166075b0ba50f30923148e875cd617c2 Mon Sep 17 00:00:00 2001 From: User Date: Sat, 15 Feb 2025 21:19:04 +0300 Subject: [PATCH] reviews --- frontend/style/components/cart-items.tsx | 86 +++++----- .../style/components/dynamic-component.tsx | 41 +++++ frontend/style/components/header.tsx | 149 ++++++++++++------ frontend/style/components/image-gallery.tsx | 68 ++++++++ frontend/style/components/review-form.tsx | 43 +++-- frontend/style/components/review-list.tsx | 58 +++++-- frontend/style/components/reviews.db | Bin 0 -> 12288 bytes frontend/style/components/reviews.go | 143 +++++++++++++++++ frontend/style/components/video-player.tsx | 16 ++ 9 files changed, 488 insertions(+), 116 deletions(-) create mode 100644 frontend/style/components/dynamic-component.tsx create mode 100644 frontend/style/components/image-gallery.tsx create mode 100644 frontend/style/components/reviews.db create mode 100644 frontend/style/components/reviews.go create mode 100644 frontend/style/components/video-player.tsx diff --git a/frontend/style/components/cart-items.tsx b/frontend/style/components/cart-items.tsx index 8b056a4a..f7bb8121 100644 --- a/frontend/style/components/cart-items.tsx +++ b/frontend/style/components/cart-items.tsx @@ -3,10 +3,11 @@ import { useState } from "react" import { Checkbox } from "./ui/checkbox" 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 { useCart } from "@/contexts/cart-context" import { useFavorites } from "@/contexts/favorites-context" +import Link from "next/link" export function CartItems() { const { items, removeFromCart, addToCart, removeAllFromCart, updateQuantity, getTotalQuantity } = useCart() @@ -14,14 +15,10 @@ export function CartItems() { const [selectedItems, setSelectedItems] = useState([]) const toggleItem = (id: number) => { - setSelectedItems(prev => - prev.includes(id) - ? prev.filter(item => item !== id) - : [...prev, id] - ) + setSelectedItems((prev) => (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)) { removeFromFavorites(item.id) } else { @@ -39,57 +36,58 @@ export function CartItems() { Всего товаров: {getTotalQuantity()}
- { - setSelectedItems(checked ? items.map(item => item.id) : []) + setSelectedItems(checked ? items.map((item) => item.id) : []) }} /> Выбрать все - {selectedItems.length > 0 && ( - - )} + {selectedItems.length > 0 && }
{items.map((item) => (
- toggleItem(item.id)} - /> - {item.title} -
-

{item.title}

-
-
- + {item.quantity} + +
+ - {item.quantity} -
- -
-
+
{item.price * item.quantity} ₽
-
- {item.price} ₽ за шт. -
+
{item.price} ₽ за шт.
))} diff --git a/frontend/style/components/dynamic-component.tsx b/frontend/style/components/dynamic-component.tsx new file mode 100644 index 00000000..7a1b6aae --- /dev/null +++ b/frontend/style/components/dynamic-component.tsx @@ -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 ( +
+

Dynamic Component

+
+ setData(e.target.value)} placeholder="Enter data" /> + +
+
+

Current data: {data}

+
+
+ ) +} + diff --git a/frontend/style/components/header.tsx b/frontend/style/components/header.tsx index a100afe9..c5c89da2 100644 --- a/frontend/style/components/header.tsx +++ b/frontend/style/components/header.tsx @@ -4,88 +4,135 @@ import { useState } from "react" import Link from "next/link" import { Search } from "./search" 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 { Sheet, SheetContent, SheetTrigger } from "./ui/sheet" +import { Sheet, SheetContent, SheetTrigger, SheetClose } from "./ui/sheet" import { useCart } from "@/contexts/cart-context" import { useFavorites } from "@/contexts/favorites-context" +import { useAuth } from "@/contexts/auth-context" import { Badge } from "./ui/badge" +import { CartSummary } from "./cart-summary" + export function Header() { const [isMenuOpen, setIsMenuOpen] = useState(false) - const { getTotalItems } = useCart() + const [isCartOpen, setIsCartOpen] = useState(false) + const { getTotalItems, getTotalUniqueItems } = useCart() const { getTotalFavorites } = useFavorites() + const { isLoggedIn } = useAuth() return (
-
-
-
- {/* Используем отступ слева для надписи STORE */} - - ETRNOS +
+
+
+ + STORE -
+
-
- -
-
-
+
+
+ +
+
- -
- - - - - - - - - + +
+ +
+ + +
+
+ + + + + +
+

Меню

+ + + +
+ +
+
+
-
- -
) -} \ No newline at end of file +} + diff --git a/frontend/style/components/image-gallery.tsx b/frontend/style/components/image-gallery.tsx new file mode 100644 index 00000000..67ce92d0 --- /dev/null +++ b/frontend/style/components/image-gallery.tsx @@ -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
Error: No images provided
; + } + + 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 ( +
+
+ {`Product +
+ + +
+ {images.map((_, index) => ( +
+
+ ) +} \ No newline at end of file diff --git a/frontend/style/components/review-form.tsx b/frontend/style/components/review-form.tsx index 9657d134..a376ca89 100644 --- a/frontend/style/components/review-form.tsx +++ b/frontend/style/components/review-form.tsx @@ -4,10 +4,11 @@ import { useState } from "react" import { Button } from "./ui/button" import { Textarea } from "./ui/textarea" import { Label } from "./ui/label" -import { Star } from 'lucide-react' +import { LogIn, Star } from 'lucide-react' import { useAuth } from "@/contexts/auth-context" import Link from "next/link" + interface ReviewFormProps { productId: number } @@ -16,19 +17,44 @@ export function ReviewForm({ productId }: ReviewFormProps) { const [rating, setRating] = useState(0) const [comment, setComment] = useState("") const { isLoggedIn } = useAuth() + const {email} = useAuth(); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - // Here you would typically send the review data to your backend - console.log("Submitting review:", { productId, rating, comment }) - // Reset form after submission - setRating(0) - setComment("") + + const reviewData = { + username: {email}=useAuth(), // Здесь можно подтянуть имя из Auth-контекста + rating, + 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) { return ( -

Чтобы оставить отзыв, пожалуйста, войдите в систему.

+

+ Чтобы оставить отзыв, пожалуйста, войдите в систему. +

) } @@ -69,4 +95,3 @@ export function ReviewForm({ productId }: ReviewFormProps) { ) } - diff --git a/frontend/style/components/review-list.tsx b/frontend/style/components/review-list.tsx index 3fae643b..6fbdd7fc 100644 --- a/frontend/style/components/review-list.tsx +++ b/frontend/style/components/review-list.tsx @@ -1,18 +1,53 @@ -import { Review } from "@/types/product" -import { Star } from 'lucide-react' +import { useEffect, useState } from "react" +import type { Review } from "@/types/product" +import { Star } from "lucide-react" import { useAuth } from "@/contexts/auth-context" import Link from "next/link" +import { Button } from "./ui/button" interface ReviewListProps { - reviews: Review[] + productId: number } -export function ReviewList({ reviews }: ReviewListProps) { +export function ReviewList({ productId }: ReviewListProps) { const { isLoggedIn } = useAuth() + const [reviews, setReviews] = useState([]) + 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) { return ( -

Чтобы просматривать отзывы, пожалуйста, войдите в систему.

+
+

Чтобы просматривать отзывы, пожалуйста, войдите в систему.

+ +
) } @@ -25,25 +60,24 @@ export function ReviewList({ reviews }: ReviewListProps) { reviews.map((review) => (
+ {/* Вывод рейтинга с помощью звезд */}
{[1, 2, 3, 4, 5].map((star) => ( ))}
- - {new Date(review.createdAt).toLocaleDateString()} - + {/* Дата отзыва */} + {new Date(review.createdAt).toLocaleDateString()}
+ {/* Комментарий */}

{review.comment}

)) )} ) + } - diff --git a/frontend/style/components/reviews.db b/frontend/style/components/reviews.db new file mode 100644 index 0000000000000000000000000000000000000000..317201216cb1a45bed6c8de6cf9699a68d02fb31 GIT binary patch literal 12288 zcmeI2&udVm`gybk-t8PNRXj_X` z306w^rEF;p?pbS)E!)faBKnrBqJ2Xue`(Y=cQ#t<0Y`D!hGMx;=m>V2PQxz1QCL#|vZVd3kaA|21da>>ds7Uc?)z4fG^WGz@|$XTn<9d0J8 zo2k*1e>P8yy-?yJ{xa^yt@!2GhuA-{7qR8oRP=q+joyxqkNrD#f)Z*(fCvx)B0vO) z01+SpM1Tkof&YiVwZw>WenP!uY}FrB-7wP)UDM4OO`l;VusN>j+|;j)k0|PdnlK_# z6=M33$Y9fPDY|+p636019C*KpH$!4=UW?(hSJW?!tXp><*XldX5GoJEe&-%^+~j74 zr!&{1((y&l5f*=lBX4g&o95cwZfNYBpMH0-rw(=0Al{07@fO58aVUd2PcKR;t!%S=Uv68`+t{eGdA=0;izOJU&a3ZfqXsxfXFvmxoY8mJYw=s<4Qz(%27 zpPN;O^_`7HPq3m4H|IIa_~(B${$9Z!H6lO+hyW2F0z`la5CI}U1c(3;AOb|-^C56S M4Nt1wKS?A$0`Nt>2mk;8 literal 0 HcmV?d00001 diff --git a/frontend/style/components/reviews.go b/frontend/style/components/reviews.go new file mode 100644 index 00000000..bccb2c72 --- /dev/null +++ b/frontend/style/components/reviews.go @@ -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, "Отзыв успешно добавлен") +} diff --git a/frontend/style/components/video-player.tsx b/frontend/style/components/video-player.tsx new file mode 100644 index 00000000..f3e76a69 --- /dev/null +++ b/frontend/style/components/video-player.tsx @@ -0,0 +1,16 @@ +"use client" + +interface VideoPlayerProps { + videoUrl: string +} + +export function VideoPlayer({ videoUrl }: VideoPlayerProps) { + return ( +
+ +
+ ) +} +