completely link frontend pages with django app rest api endpoints and verify cypress tests pass
This commit is contained in:
parent
9bb761f176
commit
2ba073b9b0
3 changed files with 233 additions and 84 deletions
62
cypress/e2e/api.cy.ts
Normal file
62
cypress/e2e/api.cy.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
describe('Seller Central Django Backend API E2E Tests', () => {
|
||||
const backendUrl = 'http://localhost:8000';
|
||||
|
||||
it('verifies product list', () => {
|
||||
cy.request(`${backendUrl}/api/products/`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.be.an('array');
|
||||
});
|
||||
});
|
||||
|
||||
it('creates and deletes a product', () => {
|
||||
const uniqueSku = `SKU-CY-${Date.now()}`;
|
||||
cy.request('POST', `${backendUrl}/api/products/`, {
|
||||
title: 'Cypress Test Silk Scarf',
|
||||
category: 'Apparel',
|
||||
price: '55.00',
|
||||
stock: 20,
|
||||
sku: uniqueSku
|
||||
}).then((response) => {
|
||||
expect(response.status).to.eq(201);
|
||||
expect(response.body.sku).to.eq(uniqueSku);
|
||||
const prodId = response.body.id;
|
||||
|
||||
// Delete the created product
|
||||
cy.request('DELETE', `${backendUrl}/api/products/${prodId}/`).then((delRes) => {
|
||||
expect(delRes.status).to.eq(204); // django rest framework delete returns 204 No Content
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies order list and acceptance', () => {
|
||||
cy.request(`${backendUrl}/api/orders/`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.be.an('array');
|
||||
expect(response.body.length).to.be.greaterThan(0);
|
||||
|
||||
const pendingOrder = response.body.find((o: any) => o.status === 'Pending Acceptance');
|
||||
if (pendingOrder) {
|
||||
cy.request('POST', `${backendUrl}/api/orders/${pendingOrder.id}/accept/`).then((acceptRes) => {
|
||||
expect(acceptRes.status).to.eq(200);
|
||||
expect(acceptRes.body.status).to.eq('Ready to Ship');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies wallet summary and payout withdrawal', () => {
|
||||
cy.request(`${backendUrl}/api/wallet/`).then((response) => {
|
||||
expect(response.status).to.eq(200);
|
||||
expect(response.body).to.have.property('outstanding');
|
||||
expect(response.body).to.have.property('withdrawn');
|
||||
|
||||
const currentOutstanding = parseFloat(response.body.outstanding);
|
||||
if (currentOutstanding > 10) {
|
||||
cy.request('POST', `${backendUrl}/api/wallet/withdraw/`, { amount: '10.00' }).then((withdrawRes) => {
|
||||
expect(withdrawRes.status).to.eq(200);
|
||||
expect(parseFloat(withdrawRes.body.outstanding)).to.eq(currentOutstanding - 10);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
247
src/App.tsx
247
src/App.tsx
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { CONFIG } from './config'
|
||||
|
||||
type Page = 'home' | 'about' | 'contact' | 'login' | 'signup' | 'profile-completion' | 'confirmation' | 'dashboard' | 'forgot-password' | 'login-otp' | 'welcome-tour'
|
||||
|
|
@ -139,6 +139,67 @@ export default function App() {
|
|||
})
|
||||
const [withdrawAmount, setWithdrawAmount] = useState('')
|
||||
|
||||
// Load data from backend on mount or when profile is complete / logged in
|
||||
useEffect(() => {
|
||||
if (isProfileComplete || currentPage === 'dashboard') {
|
||||
// Fetch Products
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/products/`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (Array.isArray(data)) setProducts(data);
|
||||
})
|
||||
.catch(err => console.error('Error fetching products:', err));
|
||||
|
||||
// Fetch Orders
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/orders/`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (Array.isArray(data)) setOrders(data);
|
||||
})
|
||||
.catch(err => console.error('Error fetching orders:', err));
|
||||
|
||||
// Fetch Returns
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/returns/`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (Array.isArray(data)) setReturns(data);
|
||||
})
|
||||
.catch(err => console.error('Error fetching returns:', err));
|
||||
|
||||
// Fetch Wallet
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/wallet/`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data) setWallet(data);
|
||||
})
|
||||
.catch(err => console.error('Error fetching wallet:', err));
|
||||
}
|
||||
}, [isProfileComplete, currentPage]);
|
||||
|
||||
const handleAcceptOrder = (id: string) => {
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/accept/`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(updatedOrder => {
|
||||
setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o))
|
||||
alert(`Order accepted successfully!`)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
const handleRejectOrder = (id: string) => {
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/orders/${id}/reject/`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(updatedOrder => {
|
||||
setOrders(orders.map(o => o.id === updatedOrder.id ? updatedOrder : o))
|
||||
alert(`Order rejected.`)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
// Bulk Upload simulations
|
||||
const [bulkLog, setBulkLog] = useState<string[]>([])
|
||||
const [isParsingBulk, setIsParsingBulk] = useState(false)
|
||||
|
|
@ -240,21 +301,34 @@ export default function App() {
|
|||
// Product creation/modification
|
||||
const handleSaveProduct = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (isEditingProduct) {
|
||||
setProducts(products.map(p => p.id === productForm.id ? { ...productForm, price: Number(productForm.price), stock: Number(productForm.stock) } : p))
|
||||
setIsEditingProduct(false)
|
||||
alert('Product modified successfully!')
|
||||
} else {
|
||||
const newProd: Product = {
|
||||
...productForm,
|
||||
id: String(products.length + 1),
|
||||
price: Number(productForm.price),
|
||||
const url = isEditingProduct
|
||||
? `${CONFIG.apiBaseUrl}/api/products/${productForm.id}/`
|
||||
: `${CONFIG.apiBaseUrl}/api/products/`
|
||||
const method = isEditingProduct ? 'PUT' : 'POST'
|
||||
|
||||
fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: productForm.title,
|
||||
category: productForm.category,
|
||||
price: String(productForm.price),
|
||||
stock: Number(productForm.stock),
|
||||
sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}`
|
||||
}
|
||||
setProducts([...products, newProd])
|
||||
alert('Product uploaded successfully!')
|
||||
}
|
||||
sku: productForm.sku || `PROD-${Date.now().toString().slice(-6)}`,
|
||||
image: productForm.image
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(() => {
|
||||
// Refresh products from backend
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/products/`)
|
||||
.then(r => r.json())
|
||||
.then(prods => setProducts(prods))
|
||||
setIsEditingProduct(false)
|
||||
alert(isEditingProduct ? 'Product modified successfully!' : 'Product uploaded successfully!')
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
|
||||
// reset form
|
||||
setProductForm({ id: '', title: '', category: 'Apparel', price: 0, stock: 0, sku: '', image: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?auto=format&fit=crop&q=80&w=100' })
|
||||
}
|
||||
|
|
@ -266,7 +340,13 @@ export default function App() {
|
|||
|
||||
const handleDeleteProduct = (id: string) => {
|
||||
if (confirm('Are you sure you want to delete this listing?')) {
|
||||
setProducts(products.filter(p => p.id !== id))
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/products/${id}/`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(() => {
|
||||
setProducts(products.filter(p => p.id !== id))
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -276,19 +356,37 @@ export default function App() {
|
|||
setIsParsingBulk(true)
|
||||
setBulkLog(['Parsing CSV file template...', 'Validating rows against inventory Schema...'])
|
||||
|
||||
const mockBulkProducts = [
|
||||
{ title: 'Hand-Carved Walnut Bowl', category: 'Kitchenware', price: '65.00', stock: 15, sku: 'BOWL-WAL-12' },
|
||||
{ title: 'Organic Cotton Tablecloth', category: 'Linens', price: '48.00', stock: 30, sku: 'LINE-COT-15' }
|
||||
]
|
||||
|
||||
setTimeout(() => {
|
||||
const parsedProducts: Product[] = [
|
||||
{ id: String(products.length + 1), title: 'Hand-Carved Walnut Bowl', category: 'Kitchenware', price: 65.00, stock: 15, sku: 'BOWL-WAL-12', image: 'https://images.unsplash.com/photo-1610701596007-11502861dcfa?auto=format&fit=crop&q=80&w=100' },
|
||||
{ id: String(products.length + 2), title: 'Organic Cotton Tablecloth', category: 'Linens', price: 48.00, stock: 30, sku: 'LINE-COT-15', image: 'https://images.unsplash.com/photo-1603006905003-be475563bc59?auto=format&fit=crop&q=80&w=100' }
|
||||
]
|
||||
setProducts(prev => [...prev, ...parsedProducts])
|
||||
setBulkLog(prev => [
|
||||
...prev,
|
||||
'Row 1: Verified SKU BOWL-WAL-12 - Added',
|
||||
'Row 2: Verified SKU LINE-COT-15 - Added',
|
||||
'Successfully added 2 new listings bulk!'
|
||||
])
|
||||
setIsParsingBulk(false)
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/products/bulk-upload/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ products: mockBulkProducts })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(() => {
|
||||
setBulkLog(prev => [
|
||||
...prev,
|
||||
'Row 1: Verified SKU BOWL-WAL-12 - Added',
|
||||
'Row 2: Verified SKU LINE-COT-15 - Added',
|
||||
'Successfully added 2 new listings bulk!'
|
||||
])
|
||||
// Refresh products
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/products/`)
|
||||
.then(r => r.json())
|
||||
.then(prods => {
|
||||
setProducts(prods)
|
||||
setIsParsingBulk(false)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
setBulkLog(prev => [...prev, 'Error during upload to server.'])
|
||||
setIsParsingBulk(false)
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
|
|
@ -300,40 +398,39 @@ export default function App() {
|
|||
alert('Please enter a valid amount')
|
||||
return
|
||||
}
|
||||
if (amount > wallet.outstanding) {
|
||||
alert('Amount exceeds outstanding ready payout balance')
|
||||
return
|
||||
}
|
||||
|
||||
const txId = `TX-${Math.floor(1000 + Math.random() * 9000)}`
|
||||
const newTx = {
|
||||
id: txId,
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
amount: amount,
|
||||
status: 'Transferred'
|
||||
}
|
||||
|
||||
setWallet({
|
||||
outstanding: wallet.outstanding - amount,
|
||||
withdrawn: wallet.withdrawn + amount,
|
||||
history: [newTx, ...wallet.history]
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/wallet/withdraw/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ amount: String(amount) })
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Insufficient funds or invalid request');
|
||||
return res.json();
|
||||
})
|
||||
.then(updatedWallet => {
|
||||
setWallet(updatedWallet)
|
||||
setWithdrawAmount('')
|
||||
alert(`Payout of $${amount} successfully transferred!`)
|
||||
})
|
||||
.catch(err => {
|
||||
alert(err.message)
|
||||
})
|
||||
setWithdrawAmount('')
|
||||
alert(`Payout of $${amount} successfully transferred!`)
|
||||
}
|
||||
|
||||
// Returns actions
|
||||
const handleReturnAction = (id: string, action: 'Approved' | 'Rejected') => {
|
||||
setReturns(returns.map(ret => {
|
||||
if (ret.id === id) {
|
||||
return {
|
||||
...ret,
|
||||
status: action === 'Approved' ? 'In Transit' : 'Rejected'
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}))
|
||||
alert(`Return request ${action.toLowerCase()}!`)
|
||||
fetch(`${CONFIG.apiBaseUrl}/api/returns/${id}/action/`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: action })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(updatedReturn => {
|
||||
setReturns(returns.map(ret => ret.id === id ? updatedReturn : ret))
|
||||
alert(`Return request ${action.toLowerCase()}!`)
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
}
|
||||
|
||||
const selectedMetrics = CONFIG.dashboardData.filters[dateFilter]
|
||||
|
|
@ -1465,7 +1562,7 @@ export default function App() {
|
|||
</td>
|
||||
<td>{p.sku}</td>
|
||||
<td>{p.category}</td>
|
||||
<td>${p.price.toFixed(2)}</td>
|
||||
<td>${Number(p.price).toFixed(2)}</td>
|
||||
<td style={{ fontWeight: 600, color: p.stock < 15 ? 'var(--error)' : 'inherit' }}>
|
||||
{p.stock} pcs
|
||||
</td>
|
||||
|
|
@ -1668,7 +1765,7 @@ export default function App() {
|
|||
<td>{o.item}</td>
|
||||
<td>{o.quantity}</td>
|
||||
<td>{o.customer}</td>
|
||||
<td style={{ fontWeight: 600 }}>${o.total.toFixed(2)}</td>
|
||||
<td style={{ fontWeight: 600 }}>${Number(o.total).toFixed(2)}</td>
|
||||
<td>
|
||||
<span className={`badge ${
|
||||
o.status === 'Delivered' ? 'badge-success' :
|
||||
|
|
@ -1685,32 +1782,14 @@ export default function App() {
|
|||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ backgroundColor: 'var(--success)', color: 'white', border: 'none', padding: '0.35rem 0.75rem', fontSize: '0.8rem', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setOrders(orders.map(item => item.id === o.id ? {
|
||||
...item,
|
||||
status: 'Ready to Ship',
|
||||
carrier: 'DHL Express',
|
||||
tracking: `DHL-${Math.floor(100000 + Math.random() * 900000)}`,
|
||||
eta: '3 Days'
|
||||
} : item));
|
||||
alert(`Order ${o.id} accepted successfully!`);
|
||||
}}
|
||||
onClick={() => handleAcceptOrder(o.id)}
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ backgroundColor: 'var(--error)', color: 'white', border: 'none', padding: '0.35rem 0.75rem', fontSize: '0.8rem', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
setOrders(orders.map(item => item.id === o.id ? {
|
||||
...item,
|
||||
status: 'Rejected',
|
||||
carrier: 'N/A',
|
||||
tracking: 'N/A',
|
||||
eta: 'N/A'
|
||||
} : item));
|
||||
alert(`Order ${o.id} rejected.`);
|
||||
}}
|
||||
onClick={() => handleRejectOrder(o.id)}
|
||||
>
|
||||
Reject
|
||||
</button>
|
||||
|
|
@ -1965,11 +2044,11 @@ export default function App() {
|
|||
<div className="metrics-row">
|
||||
<div className="metric-data-card">
|
||||
<div className="metric-title">Outstanding Ready Balance</div>
|
||||
<div className="metric-value" style={{ color: 'var(--success)' }}>${wallet.outstanding.toFixed(2)}</div>
|
||||
<div className="metric-value" style={{ color: 'var(--success)' }}>${Number(wallet.outstanding).toFixed(2)}</div>
|
||||
</div>
|
||||
<div className="metric-data-card">
|
||||
<div className="metric-title">Total Payouts Withdrawn</div>
|
||||
<div className="metric-value">${wallet.withdrawn.toFixed(2)}</div>
|
||||
<div className="metric-value">${Number(wallet.withdrawn).toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -2010,11 +2089,11 @@ export default function App() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{wallet.history.map(tx => (
|
||||
<tr key={tx.id}>
|
||||
<td>{tx.id}</td>
|
||||
{(wallet.history || (wallet as any).transactions || []).map((tx: any) => (
|
||||
<tr key={tx.id || tx.tx_id}>
|
||||
<td>{tx.id || tx.tx_id}</td>
|
||||
<td>{tx.date}</td>
|
||||
<td style={{ fontWeight: 600 }}>${tx.amount.toFixed(2)}</td>
|
||||
<td style={{ fontWeight: 600 }}>${Number(tx.amount).toFixed(2)}</td>
|
||||
<td><span className="badge badge-success">{tx.status}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
const getApiBaseUrl = () => {
|
||||
if (typeof window !== 'undefined' && window.location.hostname === 'betasuppliers.tipro.in') {
|
||||
return 'http://16.113.57.127:8000';
|
||||
}
|
||||
return 'http://localhost:8000';
|
||||
};
|
||||
|
||||
export const CONFIG = {
|
||||
apiBaseUrl: getApiBaseUrl(),
|
||||
companyName: 'Global Artisans Hub',
|
||||
logoLetter: 'A',
|
||||
supportEmail: 'support@globalartisanshub.com',
|
||||
|
|
|
|||
Loading…
Reference in a new issue