This commit is contained in:
NotII
2025-03-23 21:25:37 +00:00
parent dc51901c5c
commit 8d7d9b9e1c
5 changed files with 74 additions and 80 deletions

View File

@@ -8,11 +8,11 @@ import { Input } from "@/components/ui/input";
import { Product } from "@/models/products"; import { Product } from "@/models/products";
import { Plus, Upload, Search, RefreshCw } from "lucide-react"; import { Plus, Upload, Search, RefreshCw } from "lucide-react";
import { import {
fetchProductData,
saveProductData, saveProductData,
saveProductImage, saveProductImage,
deleteProductData, deleteProductData,
} from "@/lib/productData"; } from "@/lib/productData";
import { clientFetch } from "@/lib/client-utils";
import { ProductModal } from "@/components/modals/product-modal"; import { ProductModal } from "@/components/modals/product-modal";
import ProductTable from "@/components/tables/product-table"; import ProductTable from "@/components/tables/product-table";
import { Category } from "@/models/categories"; import { Category } from "@/models/categories";
@@ -55,12 +55,9 @@ export default function ProductsPage() {
try { try {
setLoading(true); setLoading(true);
const productsUrl = `${process.env.NEXT_PUBLIC_API_URL}/products`;
const categoriesUrl = `${process.env.NEXT_PUBLIC_API_URL}/categories`;
const [fetchedProducts, fetchedCategories] = await Promise.all([ const [fetchedProducts, fetchedCategories] = await Promise.all([
fetchProductData(productsUrl, authToken), clientFetch('/products'),
fetchProductData(categoriesUrl, authToken), clientFetch('/categories'),
]); ]);
console.log("Fetched Products:", fetchedProducts); console.log("Fetched Products:", fetchedProducts);
@@ -76,6 +73,7 @@ export default function ProductsPage() {
setLoading(false); setLoading(false);
} catch (error) { } catch (error) {
console.error("Error fetching data:", error); console.error("Error fetching data:", error);
toast.error("Failed to load products");
setLoading(false); setLoading(false);
} }
}; };
@@ -115,67 +113,41 @@ export default function ProductsPage() {
const handleSaveProduct = async (data: Product, file?: File | null) => { const handleSaveProduct = async (data: Product, file?: File | null) => {
const authToken = document.cookie try {
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
if (!authToken) {
router.push("/login");
return;
}
setLoading(true); setLoading(true);
try { if (editing && !data._id) {
const url = editing throw new Error("Cannot update product without an ID");
? `${process.env.NEXT_PUBLIC_API_URL}/products/${data._id}`
: `${process.env.NEXT_PUBLIC_API_URL}/products`;
// Save product data
const savedProduct = await saveProductData(
url,
{
name: data.name,
description: data.description,
unitType: data.unitType,
category: data.category,
pricing: data.pricing,
stockTracking: data.stockTracking,
currentStock: data.currentStock,
lowStockThreshold: data.lowStockThreshold
},
authToken,
editing ? "PUT" : "POST"
);
if (file) {
const imageUrl = `${process.env.NEXT_PUBLIC_API_URL}/products/${savedProduct._id}/image`;
await saveProductImage(
imageUrl,
file,
authToken
);
} }
// If editing and stock values were updated, update stock in the dedicated endpoint // Save the product data
if (editing && data.stockTracking !== undefined) { const endpoint = editing ? `/products/${data._id}` : "/products";
const stockUrl = `${process.env.NEXT_PUBLIC_API_URL}/stock/${data._id}`; const method = editing ? "PUT" : "POST";
await saveProductData(
stockUrl, const productResponse = await clientFetch(endpoint, {
{ method,
stockTracking: data.stockTracking, headers: {
currentStock: data.currentStock || 0, "Content-Type": "application/json",
lowStockThreshold: data.lowStockThreshold || 10
}, },
authToken, body: JSON.stringify(data),
"PUT" });
);
// If there's a new image to upload
if (file) {
const imageEndpoint = `/products/${productResponse._id || data._id}/image`;
const formData = new FormData();
formData.append("file", file);
await clientFetch(imageEndpoint, {
method: "PUT",
body: formData,
headers: {}, // Let the browser set the content-type for FormData
});
} }
// Refresh products list // Refresh products list
const productsUrl = `${process.env.NEXT_PUBLIC_API_URL}/products`; const fetchedProducts = await clientFetch('/products');
const fetchedProducts = await fetchProductData(productsUrl, authToken);
setProducts(fetchedProducts); setProducts(fetchedProducts);
setModalOpen(false); setModalOpen(false);
@@ -195,25 +167,23 @@ export default function ProductsPage() {
const handleDeleteProduct = async (productId: string) => { const handleDeleteProduct = async (productId: string) => {
if (!confirm("Are you sure you want to delete this product?")) return; if (!confirm("Are you sure you want to delete this product?")) return;
const authToken = document.cookie
.split("; ")
.find((row) => row.startsWith("Authorization="))
?.split("=")[1];
if (!authToken) {
router.push("/login");
return;
}
try { try {
const url = `${process.env.NEXT_PUBLIC_API_URL}/products/${productId}`; setLoading(true);
await deleteProductData(url, authToken);
await clientFetch(`/products/${productId}`, {
method: "DELETE",
});
// Refresh products list
const fetchedProducts = await clientFetch('/products');
setProducts(fetchedProducts);
setProducts(products.filter((p) => p._id !== productId));
toast.success("Product deleted successfully"); toast.success("Product deleted successfully");
setLoading(false);
} catch (error) { } catch (error) {
console.error("Error deleting product:", error); console.error(error);
toast.error("Failed to delete product"); toast.error("Failed to delete product");
setLoading(false);
} }
}; };

View File

@@ -8,7 +8,7 @@ const KeepOnline = () => {
if(window.location.pathname.includes("/dashboard")){ if(window.location.pathname.includes("/dashboard")){
const updateOnlineStatus = () => { const updateOnlineStatus = () => {
console.log("Updating online status..."); console.log("Updating online status...");
clientFetch("/auth/me"); clientFetch('/auth/me');
} }
updateOnlineStatus(); updateOnlineStatus();

View File

@@ -14,7 +14,16 @@ export async function clientFetch(url: string, options: RequestInit = {}): Promi
...options.headers, ...options.headers,
}; };
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${url}`, { // Ensure the url doesn't start with a slash if it's going to be appended to a URL that ends with one
const cleanUrl = url.startsWith('/') ? url.substring(1) : url;
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '/api';
// Ensure there's only one slash between the base URL and endpoint
const fullUrl = baseUrl.endsWith('/')
? `${baseUrl}${cleanUrl}`
: `${baseUrl}/${cleanUrl}`;
const res = await fetch(fullUrl, {
...options, ...options,
headers, headers,
}); });

View File

@@ -13,9 +13,21 @@ export async function fetchServer<T = unknown>(
if (!authToken) redirect('/login'); if (!authToken) redirect('/login');
// Ensure the endpoint doesn't start with a slash if it's going to be appended to a URL that ends with one
const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
try { try {
console.log(`${endpoint}`) // Make sure we're using a complete URL (protocol + hostname + path)
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${endpoint}`, { const apiUrl = process.env.SERVER_API_URL || 'https://internal-api.inboxi.ng/api';
// Ensure there's only one slash between the base URL and endpoint
const url = apiUrl.endsWith('/')
? `${apiUrl}${cleanEndpoint}`
: `${apiUrl}/${cleanEndpoint}`;
console.log(`Making server request to: ${url}`);
const res = await fetch(url, {
...options, ...options,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View File

@@ -10,7 +10,10 @@ export async function middleware(req: NextRequest) {
} }
try { try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/me`, { // Need to use a full URL for server-side fetch
const baseUrl = req.nextUrl.origin + '/api';
const res = await fetch(`${baseUrl}/auth/me`, {
method: "GET", method: "GET",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",