Base UI design, May contain bugs
This commit is contained in:
167
frontend/src/pages/Favorites.jsx
Normal file
167
frontend/src/pages/Favorites.jsx
Normal 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
137
frontend/src/pages/Home.jsx
Normal 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;
|
||||
237
frontend/src/pages/ProductDetail.jsx
Normal file
237
frontend/src/pages/ProductDetail.jsx
Normal 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;
|
||||
13
frontend/src/pages/Selling.jsx
Normal file
13
frontend/src/pages/Selling.jsx
Normal 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;
|
||||
281
frontend/src/pages/Settings.jsx
Normal file
281
frontend/src/pages/Settings.jsx
Normal 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;
|
||||
13
frontend/src/pages/Transactions.jsx
Normal file
13
frontend/src/pages/Transactions.jsx
Normal 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;
|
||||
Reference in New Issue
Block a user