diff --git a/frontend/public/icon/apple-touch-icon.png b/frontend/public/icon/apple-touch-icon.png deleted file mode 100644 index b69ea23..0000000 Binary files a/frontend/public/icon/apple-touch-icon.png and /dev/null differ diff --git a/frontend/public/icon/favicon.ico b/frontend/public/icon/favicon.ico index 3c826fc..84fef13 100644 Binary files a/frontend/public/icon/favicon.ico and b/frontend/public/icon/favicon.ico differ diff --git a/frontend/public/icon/icon-192-maskable.png b/frontend/public/icon/icon-192-maskable.png deleted file mode 100644 index 5913259..0000000 Binary files a/frontend/public/icon/icon-192-maskable.png and /dev/null differ diff --git a/frontend/public/icon/icon-192.png b/frontend/public/icon/icon-192.png deleted file mode 100644 index 97f48c7..0000000 Binary files a/frontend/public/icon/icon-192.png and /dev/null differ diff --git a/frontend/public/icon/icon-512-maskable.png b/frontend/public/icon/icon-512-maskable.png deleted file mode 100644 index 4066f48..0000000 Binary files a/frontend/public/icon/icon-512-maskable.png and /dev/null differ diff --git a/frontend/public/icon/icon-512.png b/frontend/public/icon/icon-512.png index c4bd39c..77f6209 100644 Binary files a/frontend/public/icon/icon-512.png and b/frontend/public/icon/icon-512.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4499afa..a3ec64f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -12,7 +12,6 @@ import Selling from "./pages/Selling"; import Transactions from "./pages/Transactions"; import Favorites from "./pages/Favorites"; import ProductDetail from "./pages/ProductDetail"; -import ItemForm from "./pages/MyListings"; import SearchPage from "./pages/SearchPage"; // Make sure to import the SearchPage function App() { @@ -712,27 +711,6 @@ function App() { } /> - {/* Add new selling routes */} - -
- -
- - } - /> - -
- -
- - } - /> { alt="Campus Plug" className="h-8 px-2" /> - + Campus Plug diff --git a/frontend/src/components/ProductForm.jsx b/frontend/src/components/ProductForm.jsx index 33c0107..f594d24 100644 --- a/frontend/src/components/ProductForm.jsx +++ b/frontend/src/components/ProductForm.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useState } from "react"; const ProductForm = ({ editingProduct, @@ -6,17 +6,60 @@ const ProductForm = ({ onSave, onCancel, }) => { + const [selectedCategory, setSelectedCategory] = useState(""); + + const categories = [ + "Electronics", + "Clothing", + "Home & Garden", + "Toys & Games", + "Books", + "Sports & Outdoors", + "Automotive", + "Beauty & Personal Care", + "Health & Wellness", + "Jewelry", + "Art & Collectibles", + "Food & Beverages", + "Office Supplies", + "Pet Supplies", + "Music & Instruments", + "Other", + ]; + + const addCategory = () => { + if ( + selectedCategory && + !(editingProduct.categories || []).includes(selectedCategory) + ) { + setEditingProduct((prev) => ({ + ...prev, + categories: [...(prev.categories || []), selectedCategory], + })); + setSelectedCategory(""); + } + }; + + const removeCategory = (categoryToRemove) => { + setEditingProduct((prev) => ({ + ...prev, + categories: (prev.categories || []).filter( + (cat) => cat !== categoryToRemove, + ), + })); + }; + return ( -
+
{/* Back Button */} -

+

{editingProduct?.id ? "Edit Your Product" : "List a New Product"}

@@ -32,7 +75,7 @@ const ProductForm = ({ onChange={(e) => setEditingProduct({ ...editingProduct, name: e.target.value }) } - className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500" + className="w-full px-4 py-2 border-2 border-gray-200 rounded-md focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" />
@@ -50,11 +93,71 @@ const ProductForm = ({ price: e.target.value, }) } - className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500" + className="w-full px-4 py-2 border-2 border-gray-200 rounded-md focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500" />
- {/* Status */} + {/* Categories - Dropdown with Add button */} +
+ +
+ + +
+ + {/* Selected Categories */} + {(editingProduct.categories || []).length > 0 ? ( +
+ {(editingProduct.categories || []).map((category) => ( + + {category} + + + ))} +
+ ) : ( +

+ Please select at least one category +

+ )} +
+ + {/* Status - Updated to Unsold/Sold */}
- {/* Images */} + {/* Description - New Field */}
+ +
+ + {/* Simplified Image Upload */} +
+ + + {/* Simple file input */} + -
- {editingProduct.images.length > 0 && - editingProduct.images.map((img, idx) => ( -
- {`Preview - +
+ ))} + {editingProduct.images.length > 0 && ( + -
- ))} -
+ )} + + + )} {/* Actions */} -
+
diff --git a/frontend/src/pages/Favorites.jsx b/frontend/src/pages/Favorites.jsx index 868eaf1..55b191d 100644 --- a/frontend/src/pages/Favorites.jsx +++ b/frontend/src/pages/Favorites.jsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; import { Link } from "react-router-dom"; -import { Heart, Tag, Trash2, Filter, ChevronDown } from "lucide-react"; +import { Heart, Tag, Trash2 } from "lucide-react"; const Favorites = () => { const [favorites, setFavorites] = useState([]); diff --git a/frontend/src/pages/MyListings.jsx b/frontend/src/pages/MyListings.jsx deleted file mode 100644 index 6cafcc7..0000000 --- a/frontend/src/pages/MyListings.jsx +++ /dev/null @@ -1,550 +0,0 @@ -import { useState, useEffect } from "react"; -import { Link, useParams, useNavigate } from "react-router-dom"; -import { ArrowLeft, Plus, X, Save, Trash } from "lucide-react"; - -const ItemForm = () => { - const { id } = useParams(); // If id exists, we are editing, otherwise creating - const navigate = useNavigate(); - const isEditing = !!id; - - const [formData, setFormData] = useState({ - title: "", - price: "", - category: "", - condition: "", - shortDescription: "", - description: "", - images: [], - status: "active", - }); - - const [originalData, setOriginalData] = useState(null); - const [errors, setErrors] = useState({}); - const [imagePreviewUrls, setImagePreviewUrls] = useState([]); - const [isLoading, setIsLoading] = useState(isEditing); - const [isSubmitting, setIsSubmitting] = useState(false); - const [showDeleteModal, setShowDeleteModal] = useState(false); - - // Categories with icons - const categories = [ - "Electronics", - "Furniture", - "Books", - "Kitchen", - "Collectibles", - "Clothing", - "Sports & Outdoors", - "Tools", - "Toys & Games", - "Other", - ]; - - // Condition options - const conditions = ["New", "Like New", "Good", "Fair", "Poor"]; - - // Status options - const statuses = ["active", "inactive", "sold", "pending"]; - - // Fetch item data if editing - useEffect(() => { - if (isEditing) { - // This would be an API call in a real app - // Simulating API call with timeout - setTimeout(() => { - // Sample data for item being edited - const itemData = { - id: parseInt(id), - title: "Dell XPS 13 Laptop - 2023 Model", - price: 850, - category: "Electronics", - condition: "Like New", - shortDescription: - "Dell XPS 13 laptop in excellent condition. Intel Core i7, 16GB RAM, 512GB SSD. Includes charger and original box.", - description: - "Selling my Dell XPS 13 laptop. Only 6 months old and in excellent condition. Intel Core i7 processor, 16GB RAM, 512GB SSD. Battery life is still excellent (around 10 hours of regular use). Comes with original charger and box. Selling because I'm upgrading to a MacBook for design work.\n\nSpecs:\n- Intel Core i7 11th Gen\n- 16GB RAM\n- 512GB NVMe SSD\n- 13.4\" FHD+ Display (1920x1200)\n- Windows 11 Pro\n- Backlit Keyboard\n- Thunderbolt 4 ports", - images: ["/image1.avif", "/image2.avif", "/image3.avif"], - status: "active", - datePosted: "2023-03-02", - }; - - setFormData(itemData); - setOriginalData(itemData); - setImagePreviewUrls(itemData.images); - setIsLoading(false); - }, 1000); - } - }, [id, isEditing]); - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData({ - ...formData, - [name]: value, - }); - - // Clear error when field is edited - if (errors[name]) { - setErrors({ - ...errors, - [name]: null, - }); - } - }; - - const handleImageChange = (e) => { - e.preventDefault(); - - const files = Array.from(e.target.files); - - if (formData.images.length + files.length > 5) { - setErrors({ - ...errors, - images: "Maximum 5 images allowed", - }); - return; - } - - // Create preview URLs for the images - const newImagePreviewUrls = [...imagePreviewUrls]; - const newImages = [...formData.images]; - - files.forEach((file) => { - const reader = new FileReader(); - reader.onloadend = () => { - newImagePreviewUrls.push(reader.result); - setImagePreviewUrls(newImagePreviewUrls); - }; - reader.readAsDataURL(file); - newImages.push(file); - }); - - setFormData({ - ...formData, - images: newImages, - }); - - // Clear error if any - if (errors.images) { - setErrors({ - ...errors, - images: null, - }); - } - }; - - const removeImage = (index) => { - const newImages = [...formData.images]; - const newImagePreviewUrls = [...imagePreviewUrls]; - - newImages.splice(index, 1); - newImagePreviewUrls.splice(index, 1); - - setFormData({ - ...formData, - images: newImages, - }); - setImagePreviewUrls(newImagePreviewUrls); - }; - - const validateForm = () => { - const newErrors = {}; - - if (!formData.title.trim()) newErrors.title = "Title is required"; - if (!formData.price) newErrors.price = "Price is required"; - if (isNaN(formData.price) || formData.price <= 0) - newErrors.price = "Price must be a positive number"; - if (!formData.category) newErrors.category = "Category is required"; - if (!formData.condition) newErrors.condition = "Condition is required"; - if (!formData.shortDescription.trim()) - newErrors.shortDescription = "Short description is required"; - if (!formData.description.trim()) - newErrors.description = "Description is required"; - if (formData.images.length === 0) - newErrors.images = "At least one image is required"; - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = (e) => { - e.preventDefault(); - - if (!validateForm()) { - // Scroll to the first error - const firstErrorField = Object.keys(errors)[0]; - document - .getElementsByName(firstErrorField)[0] - ?.scrollIntoView({ behavior: "smooth" }); - return; - } - - setIsSubmitting(true); - - // Simulate API call to post/update the item - setTimeout(() => { - console.log("Form submitted:", formData); - setIsSubmitting(false); - - // Show success and redirect to listings - alert(`Item successfully ${isEditing ? "updated" : "created"}!`); - navigate("/selling"); - }, 1500); - }; - - const handleDelete = () => { - setIsSubmitting(true); - - // Simulate API call to delete the item - setTimeout(() => { - console.log("Item deleted:", id); - setIsSubmitting(false); - setShowDeleteModal(false); - - // Show success and redirect to listings - alert("Item successfully deleted!"); - navigate("/selling"); - }, 1500); - }; - - // Show loading state if necessary - if (isLoading) { - return ( -
-
- - - Back to listings - -
-
-
-
-
-
-
- ); - } - - return ( -
- {/* Breadcrumb & Back Link */} -
- - - Back to listings - -
- -
-

- {isEditing ? "Edit Item" : "Create New Listing"} -

- - {isEditing && ( - - )} -
- -
- {/* Title */} -
- - - {errors.title && ( -

{errors.title}

- )} -
- - {/* Price, Category, Status (side by side on larger screens) */} -
-
- - - {errors.price && ( -

{errors.price}

- )} -
- -
- - - {errors.category && ( -

{errors.category}

- )} -
- - {isEditing && ( -
- - -
- )} -
- - {/* Condition */} -
- -
- {conditions.map((condition) => ( - - ))} -
- {errors.condition && ( -

{errors.condition}

- )} -
- - {/* Short Description */} -
- - -

- {formData.shortDescription.length}/150 characters -

- {errors.shortDescription && ( -

{errors.shortDescription}

- )} -
- - {/* Full Description */} -
- - -

- Use blank lines to separate paragraphs. -

- {errors.description && ( -

{errors.description}

- )} -
- - {/* Image Upload */} -
- - - {/* Image Preview Area */} -
- {imagePreviewUrls.map((url, index) => ( -
- {`Preview - -
- ))} - - {/* Upload Button (only show if less than 5 images) */} - {formData.images.length < 5 && ( - - )} -
- {errors.images && ( -

{errors.images}

- )} -
- - {/* Submit Button */} -
- -
-
- - {/* Delete Confirmation Modal */} - {showDeleteModal && ( -
-
-

- Delete Listing -

-

- Are you sure you want to delete {formData.title}? - This action cannot be undone. -

-
- - -
-
-
- )} -
- ); -}; - -export default ItemForm; diff --git a/frontend/src/pages/SearchPage.jsx b/frontend/src/pages/SearchPage.jsx index 0bcd006..0885ab1 100644 --- a/frontend/src/pages/SearchPage.jsx +++ b/frontend/src/pages/SearchPage.jsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect } from "react"; -import { Filter, Grid, Heart, Tag, X } from "lucide-react"; +import { useState, useEffect } from "react"; +import { X } from "lucide-react"; import { useLocation, Link } from "react-router-dom"; import axios from "axios"; diff --git a/frontend/src/pages/Selling.jsx b/frontend/src/pages/Selling.jsx index 3495698..99de591 100644 --- a/frontend/src/pages/Selling.jsx +++ b/frontend/src/pages/Selling.jsx @@ -1,152 +1,221 @@ -import { useState } from "react"; -import { Pencil, Trash2, Plus } from "lucide-react"; +import { useState, useEffect } from "react"; import ProductForm from "../components/ProductForm"; const Selling = () => { - const [products, setProducts] = useState([ - { - id: 1, - name: "Green Sofa", - price: 299, - status: "Active", - images: [], - }, - { - id: 2, - name: "Wooden Table", - price: 150, - status: "Inactive", - images: [], - }, - ]); + // State to store user's products + const [products, setProducts] = useState([]); + // State to control when editing form is shown + const [showForm, setShowForm] = useState(false); + // State to store the product being edited (or empty for new product) + const [editingProduct, setEditingProduct] = useState({ + name: "", + price: "", + description: "", + categories: [], + status: "Unsold", + images: [], + }); - const [editingProduct, setEditingProduct] = useState(null); - const [view, setView] = useState("list"); // "list" or "form" + // Simulate fetching products from API/database on component mount + useEffect(() => { + // This would be replaced with a real API call + const fetchProducts = async () => { + // Mock data + const mockProducts = [ + { + id: "1", + name: "Vintage Camera", + price: "299.99", + description: "A beautiful vintage film camera in excellent condition", + categories: ["Electronics", "Art & Collectibles"], + status: "Unsold", + images: ["/public/Pictures/Dell1.jpg"], + }, + { + id: "2", + name: "Leather Jacket", + price: "149.50", + description: "Genuine leather jacket, worn only a few times", + categories: ["Clothing"], + status: "Unsold", + images: [], + }, + ]; - const handleEdit = (product) => { - setEditingProduct({ ...product }); - setView("form"); - }; + setProducts(mockProducts); + }; - const handleAddNew = () => { - setEditingProduct({ - id: null, - name: "", - price: "", - status: "Active", - images: [], - }); - setView("form"); - }; + fetchProducts(); + }, []); - const handleDelete = (id) => { - setProducts((prev) => prev.filter((p) => p.id !== id)); - }; - - const handleSave = () => { - if (!editingProduct.name || !editingProduct.price) { - alert("Please enter a name and price."); - return; - } - if (editingProduct.images.length < 1) { - alert("Please upload at least one image."); - return; - } - - if (editingProduct.id === null) { + // Handle creating or updating a product + const handleSaveProduct = () => { + if (editingProduct.id) { + // Update existing product + setProducts( + products.map((p) => (p.id === editingProduct.id ? editingProduct : p)), + ); + } else { + // Create new product const newProduct = { ...editingProduct, - id: Date.now(), + id: Date.now().toString(), // Generate a temporary ID }; - setProducts((prev) => [newProduct, ...prev]); - } else { - setProducts((prev) => - prev.map((p) => (p.id === editingProduct.id ? editingProduct : p)), - ); + setProducts([...products, newProduct]); } - setEditingProduct(null); - setView("list"); + // Reset form and hide it + setShowForm(false); + setEditingProduct({ + name: "", + price: "", + description: "", + categories: [], + status: "Unsold", + images: [], + }); }; - const handleCancel = () => { - setEditingProduct(null); - setView("list"); + // Handle product deletion + const handleDeleteProduct = (productId) => { + if (window.confirm("Are you sure you want to delete this product?")) { + setProducts(products.filter((p) => p.id !== productId)); + } + }; + + // Handle editing a product + const handleEditProduct = (product) => { + setEditingProduct({ + ...product, + images: product.images || [], // Ensure images array exists + }); + setShowForm(true); + }; + + // Handle adding a new product + const handleAddProduct = () => { + setEditingProduct({ + name: "", + price: "", + description: "", + categories: [], + status: "Unsold", + images: [], + }); + setShowForm(true); }; return ( -
- {view === "list" && ( - <> -
-

My Listings

- -
+
+
+

My Listings

+ {!showForm && ( + + )} +
-
    - {products.map((product) => ( -
  • -
    -
    - {product.images.length > 0 ? ( - Product - ) : ( - No Image - )} -
    -
    -

    {product.name}

    -

    ${product.price}

    -

    - {product.status} -

    -
    -
    -
    - - -
    -
  • - ))} -
- - )} - - {view === "form" && ( + {showForm ? ( setShowForm(false)} /> + ) : ( + <> + {products.length === 0 ? ( +
+

+ You don't have any listings yet +

+ +
+ ) : ( +
+ {products.map((product) => ( +
+
+ {product.images && product.images.length > 0 ? ( + {product.name} + ) : ( +
No image
+ )} +
+ +
+
+

+ {product.name} +

+ + {product.status} + +
+ +

+ ${product.price} +

+ + {product.categories && product.categories.length > 0 && ( +
+ {product.categories.map((category) => ( + + {category} + + ))} +
+ )} + +

+ {product.description} +

+ +
+ + +
+
+
+ ))} +
+ )} + )}
); diff --git a/frontend/src/pages/Transactions.jsx b/frontend/src/pages/Transactions.jsx index 800f0b7..c38b5fc 100644 --- a/frontend/src/pages/Transactions.jsx +++ b/frontend/src/pages/Transactions.jsx @@ -1,13 +1,8 @@ -import { useState } from 'react'; -import { Link } from 'react-router-dom'; -import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from 'lucide-react'; +import { useState } from "react"; +import { Link } from "react-router-dom"; const Transactions = () => { - return ( -
- -
- ); + return
; }; -export default Transactions; \ No newline at end of file +export default Transactions;