Skip to content
Merged

fix #50

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/api/cms-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import type {
} from "../types/cms";
import { API_BASE_URL, apiCall } from "./utils";

// ==================== HOME PAGE ====================

export const fetchHomeSettings = async (): Promise<HomePageContent> => {
try {
const response = await apiCall(`${API_BASE_URL}/api/settings/home`, {
Expand Down Expand Up @@ -54,8 +52,6 @@ export const saveHomeSettings = async (settings: HomePageContent): Promise<void>
}
};

// ==================== FAQ PAGE ====================

export const fetchFaqSettings = async (): Promise<FaqPageContent> => {
try {
const response = await apiCall(`${API_BASE_URL}/api/settings/faq`, {
Expand Down Expand Up @@ -102,8 +98,6 @@ export const saveFaqSettings = async (settings: FaqPageContent): Promise<void> =
}
};

// ==================== CONTACT PAGE ====================

export const fetchContactSettings = async (): Promise<ContactPageContent> => {
try {
const response = await apiCall(`${API_BASE_URL}/api/settings/contact`, {
Expand Down Expand Up @@ -152,8 +146,6 @@ export const saveContactSettings = async (settings: ContactPageContent): Promise
}
};

// ==================== LEGACY GLOBAL SETTINGS (deprecated) ====================

export const fetchGlobalSettings = async (): Promise<GlobalSettings> => {
const jwtToken = localStorage.getItem("token");

Expand Down
8 changes: 6 additions & 2 deletions src/api/order-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { OrderCreateRequest, OrderCreateResponse, OrderDetailsResponse } from "../types/orders";
import type {
OrderCreateRequest,
OrderCreateResponse,
OrderDetailsResponse,
} from "../types/orders";
import { API_BASE_URL, apiCall } from "./utils";

export const fetchUserOrders = async (): Promise<OrderDetailsResponse[]> => {
Expand Down Expand Up @@ -80,4 +84,4 @@ export const createOrder = async (order: OrderCreateRequest): Promise<OrderCreat
console.error("API Error creating order:", error);
throw error;
}
};
};
13 changes: 0 additions & 13 deletions src/api/product-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export const getRootCategories = async (): Promise<GetCategoriesResponse> => {
}
};

// Get all categories (flat list)
export const getAllCategories = async (): Promise<GetCategoriesResponse> => {
try {
const response = await apiCall(CATEGORY_API, {
Expand All @@ -85,7 +84,6 @@ export const getAllCategories = async (): Promise<GetCategoriesResponse> => {
}
};

// Get subcategories of a specific category
export const getSubcategories = async (categoryId: string): Promise<GetCategoriesResponse> => {
try {
const response = await apiCall(`${CATEGORY_API}/${categoryId}/subcategories`, {
Expand All @@ -103,7 +101,6 @@ export const getSubcategories = async (categoryId: string): Promise<GetCategorie
}
};

// Get single category by ID
export const getCategoryById = async (categoryId: string): Promise<CategoryFromAPI> => {
try {
const response = await apiCall(`${CATEGORY_API}/${categoryId}`, {
Expand All @@ -121,11 +118,6 @@ export const getCategoryById = async (categoryId: string): Promise<CategoryFromA
}
};

// Create new category
// Supports 3 modes:
// 1. LEAF: only parentCategoryId (add as leaf)
// 2. SPLIT: parentCategoryId + childCategoryId (insert between parent and specific child)
// 3. SPLIT_ALL: only parentCategoryId (insert between parent and all children)
export const createCategory = async (
categoryData: CategoryCreateRequestDTO,
): Promise<CategoryCreateResponseDTO> => {
Expand All @@ -146,8 +138,6 @@ export const createCategory = async (
}
};

// Update category (name and/or parent)
// Note: This endpoint is not yet implemented in CategoryController, only in service
export const updateCategory = async (
categoryId: string,
categoryData: CategoryUpdateRequestDTO,
Expand All @@ -169,9 +159,6 @@ export const updateCategory = async (
}
};

// Delete category
// Note: This endpoint is not yet implemented in CategoryController, only in service
// When implemented, subcategories will be moved to the parent of deleted category
export const deleteCategory = async (categoryId: string): Promise<void> => {
try {
const response = await apiCall(`${CATEGORY_API}/${categoryId}`, {
Expand Down
13 changes: 0 additions & 13 deletions src/api/statistics-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,11 @@ import { apiCall } from "./utils";
const STATISTICS_SERVICE_URL = "http://localhost:8700";
const STATISTICS_API = `${STATISTICS_SERVICE_URL}/api/statistics`;

/**
* Get all available variants with their sales and stock data status
*/
export const getAvailableVariants = async (): Promise<VariantInfoDTO[]> => {
const response = await apiCall(`${STATISTICS_API}/variants`);
return await response.json();
};

/**
* Get sales data over time for a specific variant
* @param variantId - UUID of the variant
* @param filters - Optional date range and trend configuration
*/
export const getVariantSalesOverTime = async (
variantId: string,
filters?: SalesFilters,
Expand All @@ -44,11 +36,6 @@ export const getVariantSalesOverTime = async (
return await response.json();
};

/**
* Get stock level data over time for a specific variant
* @param variantId - UUID of the variant
* @param filters - Optional date range and trend configuration
*/
export const getVariantStockOverTime = async (
variantId: string,
filters?: StockFilters,
Expand Down
8 changes: 1 addition & 7 deletions src/components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ const Header: React.FC<HeaderProps> = ({ minimalist }) => {

const cartItemsCount = cartItems.reduce((sum, item) => sum + item.quantity, 0);

// Prefetch HomePage data when not on homepage
useEffect(() => {
if (location.pathname !== "/") {
const prefetchHomeData = async () => {
Expand All @@ -93,11 +92,9 @@ const Header: React.FC<HeaderProps> = ({ minimalist }) => {
const CACHE_KEY_CATEGORIES = "homepage_categories";
const CACHE_DURATION = 5 * 60 * 1000;

// Check cache
const cachedHome = sessionStorage.getItem(CACHE_KEY_HOME);
const cachedCategories = sessionStorage.getItem(CACHE_KEY_CATEGORIES);

// Prefetch only if there's no cache or cache is old
if (!cachedHome || Date.now() - JSON.parse(cachedHome).timestamp > CACHE_DURATION) {
fetchHomeSettings().then((data) => {
sessionStorage.setItem(
Expand All @@ -119,11 +116,10 @@ const Header: React.FC<HeaderProps> = ({ minimalist }) => {
});
}
} catch {
// Ignore prefetch errors
// Silently ignore prefetch errors
}
};

// Prefetch after a short delay to avoid blocking main rendering
const timeoutId = setTimeout(prefetchHomeData, 1000);
return () => clearTimeout(timeoutId);
}
Expand Down Expand Up @@ -523,7 +519,6 @@ const Header: React.FC<HeaderProps> = ({ minimalist }) => {
</Toolbar>
</AppBar>

{/* User menu */}
<Menu
anchorEl={userMenuAnchor}
open={Boolean(userMenuAnchor)}
Expand All @@ -548,7 +543,6 @@ const Header: React.FC<HeaderProps> = ({ minimalist }) => {
},
}}
>
{/* User Panel */}
<Box sx={{ px: 1, py: 0.5 }}>
<Typography
variant="caption"
Expand Down
8 changes: 5 additions & 3 deletions src/components/orders/OrderRow.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState, useEffect } from "react";
import { getAllVariantDetails } from "../../api/product-service";
import { type OrderDetailsResponse, type OrderItemDetails } from "../../types/orders.ts";
import { getStatusColor, getStatusLabel } from "../../types/orders.ts";
import { formatDate } from "../../utils/string.ts";
import { getAllVariantDetails } from "../../api/product-service";
import type { GetVariantResponseDTO } from "../../types/products";
import { formatDate } from "../../utils/string.ts";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import {
Expand Down Expand Up @@ -141,7 +141,9 @@ const OrderItemRow: React.FC<OrderItemRowProps> = ({ item, index }) => {
)}
<ListItemText
primary={
<Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 0.5, flexWrap: "wrap" }}>
<Box
sx={{ display: "flex", alignItems: "center", gap: 1, mb: 0.5, flexWrap: "wrap" }}
>
<Typography variant="body2" fontWeight={600}>
{productName}
</Typography>
Expand Down
24 changes: 1 addition & 23 deletions src/contexts/CartProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ export default function CartProvider({ children }: { children: ReactNode }) {
const [hasBeenCleared, setHasBeenCleared] = useState(false);

const refetchCart = useCallback(async () => {
// Don't refetch if cart was manually cleared
if (hasBeenCleared) {
return;
}

// Check if token exists before trying to fetch cart
const token = localStorage.getItem("token");
if (!token) {
setCartItems([]);
Expand All @@ -37,11 +35,9 @@ export default function CartProvider({ children }: { children: ReactNode }) {
const fetchedCart = await fetchCartProducts();
const rawItems = fetchedCart.productDtos || [];

// Map each cart item (productId is variantId) to CartItem by fetching variant details
const completeItems: CartItem[] = await Promise.all(
rawItems.map(async (rawItem) => {
try {
// productId in cart is actually variantId
const variantDetails = await getVariantDetails(rawItem.productId);
const variant = variantDetails.variant;
const mainImage =
Expand All @@ -59,7 +55,6 @@ export default function CartProvider({ children }: { children: ReactNode }) {
`Failed to fetch variant details for variantId ${rawItem.productId}:`,
err,
);
// Return a placeholder item if variant fetch fails
return {
id: rawItem.productId,
name: `Product ${rawItem.productId}`,
Expand All @@ -74,8 +69,6 @@ export default function CartProvider({ children }: { children: ReactNode }) {
setCartItems(completeItems);
} catch (err) {
console.error("Error fetching cart:", err);
// Only set error if token exists (user is logged in)
// If token doesn't exist, it's not an error - just empty cart
if (token) {
setError("Failed to fetch cart items");
} else {
Expand All @@ -89,15 +82,12 @@ export default function CartProvider({ children }: { children: ReactNode }) {

const removeProduct = useCallback(
async (productId: string) => {
// Optimistic update
setCartItems((prevItems) => prevItems.filter((item) => item.id !== productId));

try {
await deleteCartProduct(productId);
// No refetch needed - optimistic update is sufficient
} catch (err) {
console.error("Error removing product:", err);
// Revert optimistic update on error
await refetchCart();
throw new Error("Failed to remove product from cart.");
}
Expand All @@ -113,7 +103,6 @@ export default function CartProvider({ children }: { children: ReactNode }) {
return;
}

// Optimistic update
setCartItems((prevItems) =>
prevItems.map((item) =>
item.id === productId ? { ...item, quantity: newQuantity } : item,
Expand All @@ -126,10 +115,8 @@ export default function CartProvider({ children }: { children: ReactNode }) {
} else {
await subtractCartProduct(productId, 1);
}
// No refetch needed - optimistic update is sufficient
} catch (err) {
console.error("Error updating quantity:", err);
// Revert optimistic update on error
await refetchCart();
throw new Error("Failed to update quantity in cart.");
}
Expand All @@ -144,7 +131,6 @@ export default function CartProvider({ children }: { children: ReactNode }) {
return;
}

// Optimistic update
setCartItems((prevItems) =>
prevItems.map((item) =>
item.id === productId ? { ...item, quantity: newQuantity } : item,
Expand All @@ -153,10 +139,8 @@ export default function CartProvider({ children }: { children: ReactNode }) {

try {
await overwriteCartProduct(productId, newQuantity);
// No refetch needed - optimistic update is sufficient
} catch (err) {
console.error("Error updating quantity:", err);
// Revert optimistic update on error
await refetchCart();
throw new Error("Failed to update quantity in cart.");
}
Expand All @@ -170,25 +154,19 @@ export default function CartProvider({ children }: { children: ReactNode }) {
await clearCart(cartItems.map((item) => item.id));
}, [cartItems]);

// Initial fetch on mount
useEffect(() => {
refetchCart();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [refetchCart]);

// Listen for token changes (login/logout)
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === "token") {
// Token was added or removed, refetch cart
refetchCart();
}
};

// Listen for storage events (works across tabs)
window.addEventListener("storage", handleStorageChange);

// Also listen for custom event (for same-tab token changes)
const handleTokenChange = () => {
refetchCart();
};
Expand Down
4 changes: 0 additions & 4 deletions src/contexts/IndividualUserProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export default function IndividualUserProvider({ children }: { children: ReactNo
return;
}

// Extract permissions from token
const perms = getPermissionsFromToken(token);
setPermissions(perms);
localStorage.setItem("permissions", JSON.stringify(perms));
Expand All @@ -37,12 +36,10 @@ export default function IndividualUserProvider({ children }: { children: ReactNo
console.error("Error fetching current user:", err);
setError("Failed to load user data.");
setCurrentUser(null);
// If token is invalid, remove it
if (err instanceof Error && err.message.includes("401")) {
localStorage.removeItem("token");
localStorage.removeItem("permissions");
setPermissions([]);
// Dispatch custom event to notify CartProvider about token removal
window.dispatchEvent(new Event("token-changed"));
}
} finally {
Expand Down Expand Up @@ -81,7 +78,6 @@ export default function IndividualUserProvider({ children }: { children: ReactNo
setCurrentUser(null);
setPermissions([]);
setError(null);
// Dispatch custom event to notify CartProvider about token removal
window.dispatchEvent(new Event("token-changed"));
}, []);

Expand Down
Loading