Base UI design, May contain bugs

This commit is contained in:
Mann Patel
2025-03-05 22:30:52 -07:00
parent 4bedeed33c
commit 49929a85da
28 changed files with 2208 additions and 367 deletions

View File

@@ -0,0 +1,167 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Heart, Tag, Trash2, Filter, ChevronDown } from 'lucide-react';
const Favorites = () => {
const [favorites, setFavorites] = useState([
{
id: 0,
title: 'Dell XPS 16 Laptop',
price: 850,
category: 'Electronics',
image: '/image1.avif',
condition: 'Like New',
seller: 'Michael T.',
datePosted: '5d ago',
dateAdded: '2023-03-08',
},
]);
const [showFilters, setShowFilters] = useState(false);
const [sortBy, setSortBy] = useState('dateAdded');
const [filterCategory, setFilterCategory] = useState('All');
// Function to remove item from favorites
const removeFromFavorites = (id) => {
setFavorites(favorites.filter(item => item.id !== id));
};
// Available categories for filtering
const categories = ['All', 'Electronics', 'Textbooks', 'Furniture', 'Kitchen', 'Other'];
// Sort favorites based on selected sort option
const sortedFavorites = [...favorites].sort((a, b) => {
if (sortBy === 'dateAdded') {
return new Date(b.dateAdded) - new Date(a.dateAdded);
} else if (sortBy === 'priceHigh') {
return b.price - a.price;
} else if (sortBy === 'priceLow') {
return a.price - b.price;
}
return 0;
});
// Filter favorites based on selected category
const filteredFavorites = filterCategory === 'All'
? sortedFavorites
: sortedFavorites.filter(item => item.category === filterCategory);
return (
<div className="max-w-6xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-800">My Favorites</h1>
<button
className="flex items-center text-gray-600 hover:text-gray-800"
onClick={() => setShowFilters(!showFilters)}
>
<Filter className="h-5 w-5 mr-1" />
<span>Filter & Sort</span>
<ChevronDown className={`h-4 w-4 ml-1 transition-transform ${showFilters ? 'rotate-180' : ''}`} />
</button>
</div>
{/* Filters and Sorting */}
{showFilters && (
<div className="bg-white border border-gray-200 p-4 mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Sort by
</label>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
>
<option value="dateAdded">Recently Added</option>
<option value="priceHigh">Price (High to Low)</option>
<option value="priceLow">Price (Low to High)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Category
</label>
<select
value={filterCategory}
onChange={(e) => setFilterCategory(e.target.value)}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
>
{categories.map((category) => (
<option key={category} value={category}>{category}</option>
))}
</select>
</div>
</div>
</div>
)}
{/* Favorites List */}
{filteredFavorites.length === 0 ? (
<div className="bg-white border border-gray-200 p-8 text-center">
<Heart className="h-12 w-12 text-gray-300 mx-auto mb-4" />
<h3 className="text-xl font-medium text-gray-700 mb-2">No favorites yet</h3>
<p className="text-gray-500 mb-4">
Items you save will appear here. Start browsing to add items to your favorites.
</p>
<Link
to="/"
className="inline-block bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4"
>
Browse Listings
</Link>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredFavorites.map((item) => (
<div key={item.id} className="bg-white border border-gray-200 hover:shadow-md transition-shadow relative">
<button
onClick={() => removeFromFavorites(item.id)}
className="absolute top-2 right-2 p-1 bg-white rounded-full shadow-sm text-red-500 hover:bg-red-50"
title="Remove from favorites"
>
<Trash2 className="h-5 w-5" />
</button>
<Link to={`/product/${item.id}`}>
<img src={item.image} alt={item.title} className="w-full h-48 object-cover" />
<div className="p-4">
<div className="flex justify-between items-start mb-2">
<h3 className="text-lg font-medium text-gray-800 leading-tight">
{item.title}
</h3>
<span className="font-semibold text-green-600">${item.price}</span>
</div>
<div className="flex items-center text-sm text-gray-500 mb-3">
<Tag className="h-4 w-4 mr-1" />
<span>{item.category}</span>
<span className="mx-2"></span>
<span>{item.condition}</span>
</div>
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
<span className="text-xs text-gray-500">Listed {item.datePosted}</span>
<span className="text-sm font-medium text-gray-700">{item.seller}</span>
</div>
</div>
</Link>
</div>
))}
</div>
)}
{/* Show count if there are favorites */}
{filteredFavorites.length > 0 && (
<div className="mt-6 text-sm text-gray-500">
Showing {filteredFavorites.length} {filteredFavorites.length === 1 ? 'item' : 'items'}
{filterCategory !== 'All' && ` in ${filterCategory}`}
</div>
)}
</div>
);
};
export default Favorites;

137
frontend/src/pages/Home.jsx Normal file
View File

@@ -0,0 +1,137 @@
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from 'lucide-react';
const Home = () => {
const navigate = useNavigate();
// Same categories
const categories = [
{ id: 1, name: 'Textbooks', icon: <Book className="h-5 w-5" /> },
{ id: 2, name: 'Electronics', icon: <Laptop className="h-5 w-5" /> },
{ id: 3, name: 'Furniture', icon: <Sofa className="h-5 w-5" /> },
{ id: 4, name: 'Kitchen', icon: <Utensils className="h-5 w-5" /> },
{ id: 5, name: 'Other', icon: <Gift className="h-5 w-5" /> },
];
// Same listings data
const [listings, setListings] = useState([
{
id: 0,
title: 'Dell XPS 16 Laptop',
price: 850,
category: 'Electronics',
image: 'image1.avif',
condition: 'Good',
seller: 'Michael T.',
datePosted: '5d ago',
isFavorite: true,
},
]);
// Toggle favorite status
const toggleFavorite = (id, e) => {
e.preventDefault(); // Prevent navigation when clicking the heart icon
setListings(
listings.map((listing) =>
listing.id === id ? { ...listing, isFavorite: !listing.isFavorite } : listing
)
);
};
const handleSelling = () => {
navigate('/selling');
}
return (
<div>
{/* Hero Section */}
<div className="bg-green-100 py-8 px-4 mb-8 shadow-2xs">
<div className="max-w-3xl mx-auto text-center">
<h1 className="text-3xl font-bold text-gray-800 mb-4">
Buy and Sell on Campus
</h1>
<p className="text-gray-600 mb-6">
The marketplace exclusively for university students. Find everything you need or sell what you don't.
</p>
<button onClick={handleSelling}
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-6 focus:outline-none focus:ring-2 focus:ring-green-400">
Post an Item
</button>
</div>
</div>
{/* Categories */}
<div className="mb-8">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Categories</h2>
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
{categories.map((category) => (
<button
key={category.id}
className="flex flex-col items-center justify-center p-4 bg-white border border-gray-200 hover:border-green-500 hover:shadow-sm"
>
<div className="flex items-center justify-center w-12 h-12 bg-green-50 text-green-600 rounded-full mb-2">
{category.icon}
</div>
<span className="text-sm font-medium text-gray-700">{category.name}</span>
</button>
))}
</div>
</div>
{/* Recent Listings */}
<div>
<h2 className="text-xl font-semibold text-gray-800 mb-4">Recent Listings</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
{listings.map((listing) => (
<Link
key={listing.id}
to={`/product/${listing.id}`}
className="bg-white border border-gray-200 hover:shadow-md transition-shadow"
>
<div className="relative">
<img src={listing.image} alt={listing.title} className="w-full h-48 object-cover" />
<button
onClick={(e) => toggleFavorite(listing.id, e)}
className="absolute top-2 right-2 p-1 bg-white rounded-full shadow-sm"
>
<Heart
className={`h-5 w-5 ${
listing.isFavorite ? 'text-red-500 fill-red-500' : 'text-gray-400'
}`}
/>
</button>
</div>
<div className="p-4">
<div className="flex justify-between items-start mb-2">
<h3 className="text-lg font-medium text-gray-800 leading-tight">
{listing.title}
</h3>
<span className="font-semibold text-green-600">${listing.price}</span>
</div>
<div className="flex items-center text-sm text-gray-500 mb-3">
<Tag className="h-4 w-4 mr-1" />
<span>{listing.category}</span>
<span className="mx-2">•</span>
<span>{listing.condition}</span>
</div>
<div className="flex justify-between items-center pt-2 border-t border-gray-100">
<span className="text-xs text-gray-500">{listing.datePosted}</span>
<span className="text-sm font-medium text-gray-700">{listing.seller}</span>
</div>
</div>
</Link>
))}
</div>
</div>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,237 @@
import { useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import { Heart, ArrowLeft, Tag, User, Calendar, Share, Flag } from 'lucide-react';
const ProductDetail = () => {
const { id } = useParams();
const [isFavorite, setIsFavorite] = useState(false);
const [showContactForm, setShowContactForm] = useState(false);
const [message, setMessage] = useState('');
const [currentImage, setCurrentImage] = useState(0);
// Sample data for demonstration
const product = [
{
id: 0,
title: 'Dell XPS 13 Laptop - 2023 Model',
price: 850,
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',
condition: 'Like New',
category: 'Electronics',
datePosted: '2023-03-02',
images: [
'/image1.avif',
'/image2.avif',
'/image3.avif'
],
seller: {
name: 'Michael T.',
rating: 4.8,
memberSince: 'January 2022',
avatar: '/Profile.jpg'
}
},
];
console.log(product[id])
const toggleFavorite = () => {
setIsFavorite(!isFavorite);
};
const handleSendMessage = (e) => {
e.preventDefault();
// TODO: this would send the message to the seller
console.log('Message sent:', message);
setMessage('');
setShowContactForm(false);
// Show confirmation or success message
alert('Message sent to seller!');
};
// Function to split description into paragraphs
const formatDescription = (text) => {
return text.split('\n\n').map((paragraph, index) => (
<p key={index} className="mb-4">
{paragraph.split('\n').map((line, i) => (
<span key={i}>
{line}
{i < paragraph.split('\n').length - 1 && <br />}
</span>
))}
</p>
));
};
// Handle image navigation
const nextImage = () => {
setCurrentImage((prev) => (prev === product.images.length - 1 ? 0 : prev + 1));
};
const prevImage = () => {
setCurrentImage((prev) => (prev === 0 ? product.images.length - 1 : prev - 1));
};
const selectImage = (index) => {
setCurrentImage(index);
};
return (
<div className="max-w-6xl mx-auto px-4 py-8">
{/* Breadcrumb & Back Link */}
<div className="mb-6">
<Link to="/" className="flex items-center text-green-600 hover:text-green-700">
<ArrowLeft className="h-4 w-4 mr-1" />
<span>Back to listings</span>
</Link>
</div>
<div className="flex flex-col md:flex-row gap-8">
{/* Left Column - Images */}
<div className="md:w-3/5">
{/* Main Image */}
<div className="bg-white border border-gray-200 mb-4 relative">
<img
src={product[id].images[currentImage]}
alt={product[id].title}
className="w-full h-auto object-contain cursor-pointer"
onClick={nextImage}
/>
</div>
{/* Thumbnail Images */}
{product[id].images.length > 1 && (
<div className="flex gap-2 overflow-x-auto pb-2">
{product[id].images.map((image, index) => (
<div
key={index}
className={`bg-white border ${currentImage === index ? 'border-green-500' : 'border-gray-200'} min-w-[100px] cursor-pointer`}
onClick={() => selectImage(index)}
>
<img
src={image}
alt={`${product[id].title} - view ${index + 1}`}
className="w-full h-auto object-cover"
/>
</div>
))}
</div>
)}
</div>
{/* Right Column - Details */}
<div className="md:w-2/5">
{/* Product Info Card */}
<div className="bg-white border border-gray-200 p-6 mb-6">
<div className="flex justify-between items-start mb-4">
<h1 className="text-2xl font-bold text-gray-800">{product[id].title}</h1>
<button
onClick={toggleFavorite}
className="p-2 hover:bg-gray-100"
>
<Heart
className={`h-6 w-6 ${isFavorite ? 'text-red-500 fill-red-500' : 'text-gray-400'}`}
/>
</button>
</div>
<div className="text-2xl font-bold text-green-600 mb-4">
${product[id].price}
</div>
<div className="flex flex-wrap gap-x-4 gap-y-2 mb-6 text-sm">
<div className="flex items-center text-gray-600">
<Tag className="h-4 w-4 mr-1" />
<span>{product[id].category}</span>
</div>
<div className="flex items-center text-gray-600">
<span className="font-medium">Condition:</span>
<span className="ml-1">{product[id].condition}</span>
</div>
<div className="flex items-center text-gray-600">
<Calendar className="h-4 w-4 mr-1" />
<span>Posted on {product[id].datePosted}</span>
</div>
</div>
{/* Short Description */}
<div className="bg-gray-50 p-4 mb-6 border border-gray-200">
<p className="text-gray-700">{product[id].shortDescription}</p>
</div>
{/* Contact Button */}
<button
onClick={() => setShowContactForm(!showContactForm)}
className="w-full bg-green-500 hover:bg-green-600 text-white font-medium py-3 px-4 mb-3"
>
Contact Seller
</button>
{/* TODO:Contact Form */}
{showContactForm && (
<div className="border border-gray-200 p-4 mb-4">
<h3 className="font-medium text-gray-800 mb-2">Message Seller</h3>
<form onSubmit={handleSendMessage}>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Hi, is this item still available?"
className="w-full p-3 border border-gray-300 h-32 mb-3 focus:outline-none focus:border-green-500"
required
></textarea>
<button
type="submit"
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4"
>
Send Message
</button>
</form>
</div>
)}
{/* Seller Info */}
<div className="pt-4 border-t border-gray-200">
<div className="flex items-center mb-3">
<div className="mr-3">
{product[id].seller.avatar ? (
<img
src={product[id].seller.avatar}
alt="Seller"
className="h-12 w-12 rounded-full"
/>
) : (
<div className="h-12 w-12 rounded-full bg-gray-200 flex items-center justify-center">
<User className="h-6 w-6 text-gray-600" />
</div>
)}
</div>
<div>
<h3 className="font-medium text-gray-800">{product[id].seller.name}</h3>
<p className="text-sm text-gray-500">Member since {product[id].seller.memberSince}</p>
</div>
</div>
<div className="text-sm text-gray-600">
<div>
<span className="font-medium">Rating:</span> {product[id].seller.rating}/5
</div>
</div>
</div>
</div>
</div>
</div>
{/* Description Section */}
<div className="mt-8">
<h2 className="text-xl font-bold text-gray-800 mb-4">Description</h2>
<div className="bg-white border border-gray-200 p-6">
<div className="text-gray-700">
{formatDescription(product[id].description)}
</div>
</div>
</div>
</div>
);
};
export default ProductDetail;

View File

@@ -0,0 +1,13 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from 'lucide-react';
const Selling = () => {
return (
<div>
</div>
);
};
export default Selling;

View File

@@ -0,0 +1,281 @@
import { useState } from 'react';
import { User, Lock, Trash2, History, Search, Shield } from 'lucide-react';
const Settings = () => {
const [userData, setUserData] = useState({
name: '',
email: '',
phone: '',
ucid: '',
currentPassword: '',
newPassword: '',
confirmPassword: ''
});
const handleInputChange = (e) => {
const { id, value } = e.target;
setUserData(prevData => ({
...prevData,
[id]: value
}));
};
const handleProfileUpdate = (e) => {
e.preventDefault();
// TODO: updated profile data to a server
console.log('Profile updated:', userData);
alert('Profile updated successfully!');
};
const handlePasswordUpdate = (e) => {
e.preventDefault();
// TODO: validate and update password
if (userData.newPassword !== userData.confirmPassword) {
alert('New passwords do not match!');
return;
}
console.log('Password updated');
// Reset password fields
setUserData(prevData => ({
...prevData,
currentPassword: '',
newPassword: '',
confirmPassword: ''
}));
alert('Password updated successfully!');
};
const handleDeleteHistory = (type) => {
// TODO: Delete the specified history
console.log(`Deleting ${type} history`);
alert(`${type} history deleted successfully!`);
};
return (
<div className="max-w-3xl mx-auto">
<h1 className="text-2xl font-bold text-gray-800 mb-6">Account Settings</h1>
{/* Profile Information Section */}
<div className="bg-white border border-gray-200 mb-6">
<div className="border-b border-gray-200 p-4">
<div className="flex items-center">
<User className="h-5 w-5 text-gray-500 mr-2" />
<h2 className="text-lg font-medium text-gray-800">Profile Information</h2>
</div>
</div>
<div className="p-4">
<form onSubmit={handleProfileUpdate}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
Full Name
</label>
<input
type="text"
id="name"
value={userData.name}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
required
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
Email Address
</label>
<input
type="email"
id="email"
value={userData.email}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
required
/>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">
Phone Number
</label>
<input
type="tel"
id="phone"
value={userData.phone}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
/>
</div>
<div>
<label htmlFor="ucid" className="block text-sm font-medium text-gray-700 mb-1">
UCID
</label>
<input
type="text"
id="ucid"
value={userData.ucid}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
required
/>
</div>
</div>
<button
type="submit"
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4"
>
Update Profile
</button>
</form>
</div>
</div>
{/* Security Section */}
<div className="bg-white border border-gray-200 mb-6">
<div className="border-b border-gray-200 p-4">
<div className="flex items-center">
<Lock className="h-5 w-5 text-gray-500 mr-2" />
<h2 className="text-lg font-medium text-gray-800">Password</h2>
</div>
</div>
<div className="p-4">
<form onSubmit={handlePasswordUpdate}>
<div className="space-y-4 mb-4">
<div>
<label htmlFor="currentPassword" className="block text-sm font-medium text-gray-700 mb-1">
Current Password
</label>
<input
type="password"
id="currentPassword"
value={userData.currentPassword}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
required
/>
</div>
<div>
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1">
New Password
</label>
<input
type="password"
id="newPassword"
value={userData.newPassword}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
required
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
Confirm New Password
</label>
<input
type="password"
id="confirmPassword"
value={userData.confirmPassword}
onChange={handleInputChange}
className="w-full p-2 border border-gray-300 focus:outline-none focus:border-green-500"
required
/>
</div>
</div>
<button
type="submit"
className="bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4"
>
Change Password
</button>
</form>
</div>
</div>
{/* Privacy Section */}
<div className="bg-white border border-gray-200 mb-6">
<div className="border-b border-gray-200 p-4">
<div className="flex items-center">
<Shield className="h-5 w-5 text-gray-500 mr-2" />
<h2 className="text-lg font-medium text-gray-800">Privacy</h2>
</div>
</div>
<div className="p-4">
<div className="space-y-4">
<div className="flex justify-between items-center pb-4 border-b border-gray-100">
<div className="flex items-start">
<Search className="h-5 w-5 text-gray-500 mr-2 mt-0.5" />
<div>
<h3 className="font-medium text-gray-800">Search History</h3>
<p className="text-sm text-gray-500">Delete all your search history on StudentMarket</p>
</div>
</div>
<button
onClick={() => handleDeleteHistory('search')}
className="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 flex items-center"
>
<Trash2 className="h-4 w-4 mr-1" />
Delete
</button>
</div>
<div className="flex justify-between items-center">
<div className="flex items-start">
<History className="h-5 w-5 text-gray-500 mr-2 mt-0.5" />
<div>
<h3 className="font-medium text-gray-800">Browsing History</h3>
<p className="text-sm text-gray-500">Delete all your browsing history on StudentMarket</p>
</div>
</div>
<button
onClick={() => handleDeleteHistory('browsing')}
className="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 flex items-center"
>
<Trash2 className="h-4 w-4 mr-1" />
Delete
</button>
</div>
</div>
</div>
</div>
{/* Delete Account (Danger Zone) */}
<div className="bg-white border border-red-200 mb-6">
<div className="border-b border-red-200 p-4 bg-red-50">
<h2 className="text-lg font-medium text-red-700">Danger Zone</h2>
</div>
<div className="p-4">
<div className="flex justify-between items-center">
<div>
<h3 className="font-medium text-gray-800">Delete Account</h3>
<p className="text-sm text-gray-500">
Once you delete your account, there is no going back. Please be certain.
</p>
</div>
<button
className="bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4"
onClick={() => {
if (window.confirm('Are you sure you want to delete your account? This action cannot be undone.')) {
console.log('Account deletion requested');
alert('Account deletion request submitted. You will receive a confirmation email.');
}
}}
>
Delete Account
</button>
</div>
</div>
</div>
</div>
);
};
export default Settings;

View File

@@ -0,0 +1,13 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Tag, Book, Laptop, Sofa, Utensils, Gift, Heart } from 'lucide-react';
const Transactions = () => {
return (
<div>
</div>
);
};
export default Transactions;