completely link frontend pages with django app rest api endpoints and verify cypress tests pass

This commit is contained in:
vickytechkey 2026-08-09 13:15:57 +05:30
parent 9bb761f176
commit 2ba073b9b0
3 changed files with 233 additions and 84 deletions

62
cypress/e2e/api.cy.ts Normal file
View 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);
});
}
});
});
});

View file

@ -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>
))}

View file

@ -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',