version11

This commit is contained in:
Flatlogic Bot 2026-03-13 02:00:47 +00:00
parent e87d7c53d1
commit d6a0b8edb5
10 changed files with 228 additions and 40 deletions

View File

@ -1,22 +1,22 @@
import React from 'react';
import { Link, Outlet } from 'react-router-dom';
import { NavLink } from 'react-router-dom';
const AdminLayout: React.FC = () => {
const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div className="flex min-h-screen bg-gray-100">
<aside className="w-64 bg-white shadow-md">
<div className="p-6 text-2xl font-bold text-gray-800">Admin Panel</div>
<nav className="mt-6">
<Link to="/admin/products" className="block py-2.5 px-4 rounded hover:bg-gray-200">Products</Link>
<Link to="/admin/categories" className="block py-2.5 px-4 rounded hover:bg-gray-200">Categories</Link>
<Link to="/admin/sellers" className="block py-2.5 px-4 rounded hover:bg-gray-200">Sellers</Link>
<aside className="w-64 bg-gray-900 text-white p-6">
<h1 className="text-2xl font-bold mb-8">Admin</h1>
<nav className="space-y-4">
<NavLink to="/admin/products" className={({isActive}) => isActive ? 'block text-blue-400' : 'block hover:text-gray-300'}>Products</NavLink>
<NavLink to="/admin/sellers" className={({isActive}) => isActive ? 'block text-blue-400' : 'block hover:text-gray-300'}>Sellers</NavLink>
<NavLink to="/admin/categories" className={({isActive}) => isActive ? 'block text-blue-400' : 'block hover:text-gray-300'}>Categories</NavLink>
</nav>
</aside>
<main className="flex-1 p-8">
<Outlet />
{children}
</main>
</div>
);
};
export default AdminLayout;
export default AdminLayout;

View File

@ -4,12 +4,15 @@ import LoginPage from './LoginPage';
import ProductList from './ProductList';
import ProductDetail from './ProductDetail';
import CartPage from './CartPage';
import UserProfile from './UserProfile';
import AdminLayout from './AdminLayout';
import AdminRoute from './AdminRoute';
import ProductManagement from './ProductManagement';
import ProductForm from './ProductForm';
import SellerManagement from './SellerManagement';
import SellerForm from './SellerForm';
import CategoryManagement from './CategoryManagement';
import CategoryForm from './CategoryForm';
import { CartProvider } from './CartContext';
import { AuthProvider } from './AuthContext';
@ -28,6 +31,7 @@ function App() {
<Route path="/product/:id" element={<ProductDetail />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/profile" element={<UserProfile />} />
</Routes>
</main>
</>
@ -38,7 +42,11 @@ function App() {
<Route path="products/create" element={<ProductForm />} />
<Route path="products/edit/:id" element={<ProductForm />} />
<Route path="categories" element={<CategoryManagement />} />
<Route path="categories/create" element={<CategoryForm />} />
<Route path="categories/edit/:id" element={<CategoryForm />} />
<Route path="sellers" element={<SellerManagement />} />
<Route path="sellers/create" element={<SellerForm />} />
<Route path="sellers/edit/:id" element={<SellerForm />} />
</Route>
</Route>
</Routes>

View File

@ -0,0 +1,34 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useNavigate, useParams } from 'react-router-dom';
const CategoryForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [formData, setFormData] = useState({ name: '' });
useEffect(() => {
if (id) {
axios.get(`http://127.0.0.1:8000/categories/${id}/`)
.then(res => setFormData(res.data));
}
}, [id]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const action = id ? axios.put(`http://127.0.0.1:8000/categories/${id}/`, formData) : axios.post('http://127.0.0.1:8000/categories/', formData);
action.then(() => navigate('/admin/categories'));
};
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">{id ? 'Edit' : 'Create'} Category</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<input className="block w-full p-2 border" placeholder="Name" value={formData.name} onChange={e => setFormData({...formData, name: e.target.value})} />
<button type="submit" className="bg-blue-500 text-white p-2 rounded">Save</button>
</form>
</div>
);
};
export default CategoryForm;

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
interface Category {
id: number;
name: string;
slug: string;
}
const CategoryManagement: React.FC = () => {
@ -39,13 +39,15 @@ const CategoryManagement: React.FC = () => {
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">Category Management</h2>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Category Management</h2>
<Link to="/admin/categories/create" className="bg-green-500 text-white px-4 py-2 rounded">Create Category</Link>
</div>
<table className="min-w-full bg-white border border-gray-200">
<thead>
<tr>
<th className="py-2 px-4 border-b">ID</th>
<th className="py-2 px-4 border-b">Name</th>
<th className="py-2 px-4 border-b">Slug</th>
<th className="py-2 px-4 border-b">Actions</th>
</tr>
</thead>
@ -54,8 +56,8 @@ const CategoryManagement: React.FC = () => {
<tr key={category.id}>
<td className="py-2 px-4 border-b">{category.id}</td>
<td className="py-2 px-4 border-b">{category.name}</td>
<td className="py-2 px-4 border-b">{category.slug}</td>
<td className="py-2 px-4 border-b">
<td className="py-2 px-4 border-b space-x-2">
<Link to={`/admin/categories/edit/${category.id}`} className="bg-blue-500 text-white px-2 py-1 rounded">Edit</Link>
<button
onClick={() => deleteCategory(category.id)}
className="bg-red-500 text-white px-2 py-1 rounded"
@ -71,4 +73,4 @@ const CategoryManagement: React.FC = () => {
);
};
export default CategoryManagement;
export default CategoryManagement;

View File

@ -5,7 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom';
const ProductForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [formData, setFormData] = useState({ name: '', price: '', description: '', category: '' });
const [formData, setFormData] = useState({ name: '', price: '', description: '', category: '', image: null as File | null });
const [categories, setCategories] = useState<{ id: number; name: string }[]>([]);
useEffect(() => {
@ -20,25 +20,35 @@ const ProductForm: React.FC = () => {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const action = id ? axios.put(`http://127.0.0.1:8000/products/${id}/`, formData) : axios.post('http://127.0.0.1:8000/products/', formData);
const data = new FormData();
data.append('name', formData.name);
data.append('price', formData.price);
data.append('description', formData.description);
data.append('category', formData.category);
if (formData.image) data.append('image', formData.image);
const action = id ? axios.put(`http://127.0.0.1:8000/products/${id}/`, data, { headers: { 'Content-Type': 'multipart/form-data' } })
: axios.post('http://127.0.0.1:8000/products/', data, { headers: { 'Content-Type': 'multipart/form-data' } });
action.then(() => navigate('/admin/products'));
};
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">{id ? 'Edit' : 'Create'} Product</h2>
<div className="p-4 bg-white rounded shadow-md">
<h2 className="text-2xl font-bold mb-4">{id ? 'Edit' : 'Create'} Product</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<input className="block w-full p-2 border" placeholder="Name" value={formData.name} onChange={e => setFormData({...formData, name: e.target.value})} />
<input className="block w-full p-2 border" placeholder="Price" value={formData.price} onChange={e => setFormData({...formData, price: e.target.value})} />
<textarea className="block w-full p-2 border" placeholder="Description" value={formData.description} onChange={e => setFormData({...formData, description: e.target.value})} />
<select className="block w-full p-2 border" value={formData.category} onChange={e => setFormData({...formData, category: e.target.value})}>
<input className="block w-full p-2 border rounded" placeholder="Name" value={formData.name} onChange={e => setFormData({...formData, name: e.target.value})} />
<input className="block w-full p-2 border rounded" placeholder="Price" value={formData.price} onChange={e => setFormData({...formData, price: e.target.value})} />
<textarea className="block w-full p-2 border rounded" placeholder="Description" value={formData.description} onChange={e => setFormData({...formData, description: e.target.value})} />
<select className="block w-full p-2 border rounded" value={formData.category} onChange={e => setFormData({...formData, category: e.target.value})}>
<option value="">Select Category</option>
{categories.map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
</select>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">Save</button>
<input type="file" onChange={e => setFormData({...formData, image: e.target.files ? e.target.files[0] : null})} className="block w-full p-2 border rounded" />
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition">Save Product</button>
</form>
</div>
);
};
export default ProductForm;
export default ProductForm;

View File

@ -10,13 +10,16 @@ interface Product {
}
const ProductList: React.FC = () => {
const [products, setProducts] = useState<Product[]>([]);
const [allProducts, setAllProducts] = useState<Product[]>([]);
const [filteredProducts, setFilteredProducts] = useState<Product[]>([]);
const [searchTerm, setSearchTerm] = useState('');
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => {
axios.get('http://127.0.0.1:8000/products/')
.then(response => {
setProducts(response.data);
setAllProducts(response.data);
setFilteredProducts(response.data);
setLoading(false);
})
.catch(error => {
@ -25,13 +28,28 @@ const ProductList: React.FC = () => {
});
}, []);
useEffect(() => {
setFilteredProducts(
allProducts.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
)
);
}, [searchTerm, allProducts]);
if (loading) return <div className="text-center p-4">Loading...</div>;
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">Products</h1>
<input
type="text"
className="w-full p-2 mb-4 border rounded"
placeholder="Search products..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{products.map(product => (
{filteredProducts.map(product => (
<ProductCard key={product.id} name={product.name} price={product.price} />
))}
</div>
@ -39,4 +57,4 @@ const ProductList: React.FC = () => {
);
};
export default ProductList;
export default ProductList;

View File

@ -0,0 +1,35 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useNavigate, useParams } from 'react-router-dom';
const SellerForm: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [formData, setFormData] = useState({ name: '', contact_email: '' });
useEffect(() => {
if (id) {
axios.get(`http://127.0.0.1:8000/sellers/${id}/`)
.then(res => setFormData(res.data));
}
}, [id]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const action = id ? axios.put(`http://127.0.0.1:8000/sellers/${id}/`, formData) : axios.post('http://127.0.0.1:8000/sellers/', formData);
action.then(() => navigate('/admin/sellers'));
};
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">{id ? 'Edit' : 'Create'} Seller</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<input className="block w-full p-2 border" placeholder="Name" value={formData.name} onChange={e => setFormData({...formData, name: e.target.value})} />
<input className="block w-full p-2 border" placeholder="Contact Email" value={formData.contact_email} onChange={e => setFormData({...formData, contact_email: e.target.value})} />
<button type="submit" className="bg-blue-500 text-white p-2 rounded">Save</button>
</form>
</div>
);
};
export default SellerForm;

View File

@ -1,10 +1,11 @@
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
interface Seller {
id: number;
store_name: string;
is_verified: boolean;
name: string;
contact_email: string;
}
const SellerManagement: React.FC = () => {
@ -39,13 +40,16 @@ const SellerManagement: React.FC = () => {
return (
<div className="p-4">
<h2 className="text-xl font-bold mb-4">Seller Management</h2>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Seller Management</h2>
<Link to="/admin/sellers/create" className="bg-green-500 text-white px-4 py-2 rounded">Create Seller</Link>
</div>
<table className="min-w-full bg-white border border-gray-200">
<thead>
<tr>
<th className="py-2 px-4 border-b">ID</th>
<th className="py-2 px-4 border-b">Store Name</th>
<th className="py-2 px-4 border-b">Verified</th>
<th className="py-2 px-4 border-b">Name</th>
<th className="py-2 px-4 border-b">Contact</th>
<th className="py-2 px-4 border-b">Actions</th>
</tr>
</thead>
@ -53,9 +57,10 @@ const SellerManagement: React.FC = () => {
{sellers.map(seller => (
<tr key={seller.id}>
<td className="py-2 px-4 border-b">{seller.id}</td>
<td className="py-2 px-4 border-b">{seller.store_name}</td>
<td className="py-2 px-4 border-b">{seller.is_verified ? 'Yes' : 'No'}</td>
<td className="py-2 px-4 border-b">
<td className="py-2 px-4 border-b">{seller.name}</td>
<td className="py-2 px-4 border-b">{seller.contact_email}</td>
<td className="py-2 px-4 border-b space-x-2">
<Link to={`/admin/sellers/edit/${seller.id}`} className="bg-blue-500 text-white px-2 py-1 rounded">Edit</Link>
<button
onClick={() => deleteSeller(seller.id)}
className="bg-red-500 text-white px-2 py-1 rounded"
@ -71,4 +76,4 @@ const SellerManagement: React.FC = () => {
);
};
export default SellerManagement;
export default SellerManagement;

View File

@ -0,0 +1,59 @@
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useAuth } from './AuthContext';
const UserProfile: React.FC = () => {
const { user, logout } = useAuth();
const [orders, setOrders] = useState<any[]>([]);
useEffect(() => {
if (user) {
axios.get(`http://127.0.0.1:8000/orders/?user=${user.id}`)
.then(res => setOrders(res.data));
}
}, [user]);
return (
<div className="p-6 max-w-4xl mx-auto">
<h2 className="text-3xl font-bold mb-6">User Profile</h2>
{user ? (
<div className="bg-white p-6 rounded-lg shadow-md mb-8">
<p className="mb-2"><strong>Username:</strong> {user.username}</p>
<button onClick={logout} className="mt-4 bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700">Logout</button>
</div>
) : (
<p>Please log in to see your profile.</p>
)}
{user && (
<div>
<h3 className="text-2xl font-bold mb-4">Order History</h3>
{orders.length > 0 ? (
<div className="bg-white p-4 rounded shadow-md">
<table className="w-full text-left">
<thead>
<tr className="border-b">
<th className="py-2">ID</th>
<th className="py-2">Date</th>
<th className="py-2">Total</th>
</tr>
</thead>
<tbody>
{orders.map(o => (
<tr key={o.id} className="border-b">
<td className="py-2">{o.id}</td>
<td className="py-2">{new Date(o.created_at).toLocaleDateString()}</td>
<td className="py-2">${o.total_price}</td>
</tr>
))}
</tbody>
</table>
</div>
) : <p>No orders found.</p>}
</div>
)}
</div>
);
};
export default UserProfile;

View File

@ -1 +1,18 @@
@import "tailwindcss";
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
body {
@apply bg-gray-50 text-gray-900;
}
}
@layer components {
.btn-primary {
@apply bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition duration-200 shadow-sm;
}
.card {
@apply bg-white p-6 rounded-xl shadow-lg border border-gray-100;
}
}