From d2d7dc2a06d30bce1123bcb19a7055889cbf0ebb Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Fri, 2 Oct 2026 13:59:21 +0530 Subject: [PATCH] feat: implement supplier inventory management and product creation workflow --- src/context/SellerContext.tsx | 1 + src/pages/dashboard/AddProductPage.tsx | 18 ++++++++++++++++- src/pages/dashboard/InventoryPage.tsx | 28 +++++++++++++++++++++++--- src/services/api.ts | 21 +++++++++++++++++++ src/types/seller.ts | 1 + 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/context/SellerContext.tsx b/src/context/SellerContext.tsx index e9cb6cb..49df998 100644 --- a/src/context/SellerContext.tsx +++ b/src/context/SellerContext.tsx @@ -160,6 +160,7 @@ export const SellerProvider: React.FC<{ children: React.ReactNode }> = ({ childr mrp: parseFloat(p.compare_at_price) || ((parseFloat(p.price) || 0) * 1.2), stock: p.stock || 0, status: p.stock === 0 ? 'out_of_stock' : p.stock < 5 ? 'low_stock' : 'active', + approvalStatus: (p.status && (p.status.toLowerCase() === 'approved' || p.status.toLowerCase() === 'active')) ? 'approved' : (p.status === 'rejected' ? 'rejected' : 'approved'), image: p.image || '/images/placeholder.jpg', salesCount: 0 }))); diff --git a/src/pages/dashboard/AddProductPage.tsx b/src/pages/dashboard/AddProductPage.tsx index 8878fc5..e5e6718 100644 --- a/src/pages/dashboard/AddProductPage.tsx +++ b/src/pages/dashboard/AddProductPage.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { useSeller } from '../../context/SellerContext'; +import { api } from '../../services/api'; import { ArrowRight, ArrowLeft, @@ -76,15 +77,30 @@ export const AddProductPage: React.FC = () => { const [autoSaved, setAutoSaved] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); - const handleFileChange = (e: React.ChangeEvent) => { + const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { + // 1. Instant local preview const reader = new FileReader(); reader.onloadend = () => { setImage(reader.result as string); simulateAutoSave(); }; reader.readAsDataURL(file); + + // 2. Upload to S3 storage bucket + try { + const presignedData = await api.getPresignedUrl('product_cover', file.type || 'image/jpeg'); + if (presignedData && presignedData.presigned_url) { + await api.uploadToS3(presignedData.presigned_url, file); + console.log('Successfully uploaded product cover to S3:', presignedData.s3_key); + if (presignedData.s3_key) { + setImage(`https://betasupplierdocumentstorage.s3.ap-south-2.amazonaws.com/${presignedData.s3_key}`); + } + } + } catch (err) { + console.warn('S3 direct upload notice (using local preview fallback):', err); + } } }; const { addProduct } = useSeller(); diff --git a/src/pages/dashboard/InventoryPage.tsx b/src/pages/dashboard/InventoryPage.tsx index 4df4431..b14cdb5 100644 --- a/src/pages/dashboard/InventoryPage.tsx +++ b/src/pages/dashboard/InventoryPage.tsx @@ -16,7 +16,10 @@ import { ChevronLeft, ChevronRight, Edit3, - ShieldCheck + ShieldCheck, + CheckCircle2, + Clock, + XCircle } from 'lucide-react'; const HEALTH_CONFIG: Record = { @@ -222,7 +225,7 @@ export const InventoryPage: React.FC = () => { - {['Craft Product & SKU', 'Category & GI Tag', 'Pricing', 'Stock Qty', 'Health', 'Actions'].map(h => ( + {['Craft Product & SKU', 'Category & GI Tag', 'Pricing', 'Stock Qty', 'Approval Status', 'Health', 'Actions'].map(h => ( ))} @@ -285,6 +288,25 @@ export const InventoryPage: React.FC = () => {
{item.salesCount} sold all-time
+ + - diff --git a/src/services/api.ts b/src/services/api.ts index 120e5bb..952de03 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -20,6 +20,27 @@ const getAuthHeaders = (isMultipart = false) => { }; export const api = { + // S3 Presigned Uploads + getPresignedUrl: async (fileType: string, contentType: string = 'image/jpeg') => { + const res = await fetch(`${BASE_URL}/profile/presigned-url/`, { + method: 'POST', + headers: getAuthHeaders(), + body: JSON.stringify({ file_type: fileType, content_type: contentType }) + }); + if (!res.ok) throw new Error('Failed to get presigned upload URL'); + return res.json(); + }, + + uploadToS3: async (uploadUrl: string, file: File) => { + const res = await fetch(uploadUrl, { + method: 'PUT', + headers: { 'Content-Type': file.type || 'image/jpeg' }, + body: file + }); + if (!res.ok) throw new Error('Failed to upload file to S3'); + return res; + }, + // Auth login: async (username: string, password?: string, otpCode?: string, otpType?: string) => { const payload: any = { diff --git a/src/types/seller.ts b/src/types/seller.ts index 50ff82e..a977ba0 100644 --- a/src/types/seller.ts +++ b/src/types/seller.ts @@ -70,6 +70,7 @@ export interface InventoryItem { mrp: number; stock: number; status: 'active' | 'low_stock' | 'out_of_stock'; + approvalStatus?: 'approved' | 'pending' | 'rejected'; image: string; salesCount: number; }
{h}
+ {((item as any).approvalStatus === 'pending' || (item as any).status === 'pending') ? ( + + + Pending Approval + + ) : (item as any).approvalStatus === 'rejected' ? ( + + + Rejected + + ) : ( + + + Approved + + )} + @@ -314,7 +336,7 @@ export const InventoryPage: React.FC = () => { ); }) : (
+

No matching craft SKUs found.