update
This commit is contained in:
@@ -6,7 +6,12 @@ const ImageWithBasePath = (props) => {
|
||||
// Combine the base path and the provided src to create the full image source URL
|
||||
// Handle both relative and absolute paths
|
||||
let fullSrc;
|
||||
if (props.src.startsWith('http')) {
|
||||
|
||||
// Check if src is provided and is a string
|
||||
if (!props.src || typeof props.src !== 'string') {
|
||||
// Use a default placeholder image if src is undefined or invalid
|
||||
fullSrc = `${base_path}assets/img/placeholder.png`;
|
||||
} else if (props.src.startsWith('http')) {
|
||||
fullSrc = props.src;
|
||||
} else {
|
||||
// Ensure there's always a slash between base_path and src
|
||||
@@ -38,7 +43,7 @@ const ImageWithBasePath = (props) => {
|
||||
// Add PropTypes validation
|
||||
ImageWithBasePath.propTypes = {
|
||||
className: PropTypes.string,
|
||||
src: PropTypes.string.isRequired, // Make 'src' required
|
||||
src: PropTypes.string, // Allow src to be optional
|
||||
alt: PropTypes.string,
|
||||
height: PropTypes.number,
|
||||
width: PropTypes.number,
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
import { productsApi } from '../../../services/productsApi';
|
||||
|
||||
// Action Types
|
||||
export const PRODUCT_ACTIONS = {
|
||||
// Fetch Products
|
||||
FETCH_PRODUCTS_REQUEST: 'FETCH_PRODUCTS_REQUEST',
|
||||
FETCH_PRODUCTS_SUCCESS: 'FETCH_PRODUCTS_SUCCESS',
|
||||
FETCH_PRODUCTS_FAILURE: 'FETCH_PRODUCTS_FAILURE',
|
||||
|
||||
// Fetch Single Product
|
||||
FETCH_PRODUCT_REQUEST: 'FETCH_PRODUCT_REQUEST',
|
||||
FETCH_PRODUCT_SUCCESS: 'FETCH_PRODUCT_SUCCESS',
|
||||
FETCH_PRODUCT_FAILURE: 'FETCH_PRODUCT_FAILURE',
|
||||
|
||||
// Create Product
|
||||
CREATE_PRODUCT_REQUEST: 'CREATE_PRODUCT_REQUEST',
|
||||
CREATE_PRODUCT_SUCCESS: 'CREATE_PRODUCT_SUCCESS',
|
||||
CREATE_PRODUCT_FAILURE: 'CREATE_PRODUCT_FAILURE',
|
||||
|
||||
// Update Product
|
||||
UPDATE_PRODUCT_REQUEST: 'UPDATE_PRODUCT_REQUEST',
|
||||
UPDATE_PRODUCT_SUCCESS: 'UPDATE_PRODUCT_SUCCESS',
|
||||
UPDATE_PRODUCT_FAILURE: 'UPDATE_PRODUCT_FAILURE',
|
||||
|
||||
// Delete Product
|
||||
DELETE_PRODUCT_REQUEST: 'DELETE_PRODUCT_REQUEST',
|
||||
DELETE_PRODUCT_SUCCESS: 'DELETE_PRODUCT_SUCCESS',
|
||||
DELETE_PRODUCT_FAILURE: 'DELETE_PRODUCT_FAILURE',
|
||||
|
||||
// Search Products
|
||||
SEARCH_PRODUCTS_REQUEST: 'SEARCH_PRODUCTS_REQUEST',
|
||||
SEARCH_PRODUCTS_SUCCESS: 'SEARCH_PRODUCTS_SUCCESS',
|
||||
SEARCH_PRODUCTS_FAILURE: 'SEARCH_PRODUCTS_FAILURE',
|
||||
|
||||
// Categories and Brands
|
||||
FETCH_CATEGORIES_SUCCESS: 'FETCH_CATEGORIES_SUCCESS',
|
||||
FETCH_BRANDS_SUCCESS: 'FETCH_BRANDS_SUCCESS',
|
||||
|
||||
// Clear States
|
||||
CLEAR_PRODUCT_ERROR: 'CLEAR_PRODUCT_ERROR',
|
||||
CLEAR_CURRENT_PRODUCT: 'CLEAR_CURRENT_PRODUCT',
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
// Fetch all products
|
||||
export const fetchProducts = (params = {}) => async (dispatch) => {
|
||||
dispatch({ type: PRODUCT_ACTIONS.FETCH_PRODUCTS_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await productsApi.getAllProducts(params);
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_PRODUCTS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch products',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch single product
|
||||
export const fetchProduct = (id) => async (dispatch) => {
|
||||
dispatch({ type: PRODUCT_ACTIONS.FETCH_PRODUCT_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await productsApi.getProductById(id);
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_PRODUCT_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_PRODUCT_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to fetch product',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Create product
|
||||
export const createProduct = (productData) => async (dispatch) => {
|
||||
dispatch({ type: PRODUCT_ACTIONS.CREATE_PRODUCT_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await productsApi.createProduct(productData);
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.CREATE_PRODUCT_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to create product',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Update product
|
||||
export const updateProduct = (id, productData) => async (dispatch) => {
|
||||
dispatch({ type: PRODUCT_ACTIONS.UPDATE_PRODUCT_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await productsApi.updateProduct(id, productData);
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.UPDATE_PRODUCT_SUCCESS,
|
||||
payload: { id, data },
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.UPDATE_PRODUCT_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to update product',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Delete product
|
||||
export const deleteProduct = (id) => async (dispatch) => {
|
||||
dispatch({ type: PRODUCT_ACTIONS.DELETE_PRODUCT_REQUEST });
|
||||
|
||||
try {
|
||||
await productsApi.deleteProduct(id);
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.DELETE_PRODUCT_SUCCESS,
|
||||
payload: id,
|
||||
});
|
||||
return id;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.DELETE_PRODUCT_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to delete product',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Search products
|
||||
export const searchProducts = (query, params = {}) => async (dispatch) => {
|
||||
dispatch({ type: PRODUCT_ACTIONS.SEARCH_PRODUCTS_REQUEST });
|
||||
|
||||
try {
|
||||
const data = await productsApi.searchProducts(query, params);
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.SEARCH_PRODUCTS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.SEARCH_PRODUCTS_FAILURE,
|
||||
payload: error.response?.data?.message || error.message || 'Failed to search products',
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch categories
|
||||
export const fetchCategories = () => async (dispatch) => {
|
||||
try {
|
||||
const data = await productsApi.getCategories();
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_CATEGORIES_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch categories:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch brands
|
||||
export const fetchBrands = () => async (dispatch) => {
|
||||
try {
|
||||
const data = await productsApi.getBrands();
|
||||
dispatch({
|
||||
type: PRODUCT_ACTIONS.FETCH_BRANDS_SUCCESS,
|
||||
payload: data,
|
||||
});
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch brands:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear error
|
||||
export const clearProductError = () => ({
|
||||
type: PRODUCT_ACTIONS.CLEAR_PRODUCT_ERROR,
|
||||
});
|
||||
|
||||
// Clear current product
|
||||
export const clearCurrentProduct = () => ({
|
||||
type: PRODUCT_ACTIONS.CLEAR_CURRENT_PRODUCT,
|
||||
});
|
||||
@@ -1,6 +1,9 @@
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
import initialState from "./initial.value";
|
||||
import productReducer from './reducers/productReducer';
|
||||
|
||||
const rootReducer = (state = initialState, action) => {
|
||||
// Legacy reducer for existing functionality
|
||||
const legacyReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case "Product_list":
|
||||
return { ...state, product_list: action.payload };
|
||||
@@ -66,4 +69,10 @@ const rootReducer = (state = initialState, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Combine reducers
|
||||
const rootReducer = combineReducers({
|
||||
legacy: legacyReducer,
|
||||
products: productReducer,
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
import { PRODUCT_ACTIONS } from '../actions/productActions';
|
||||
|
||||
const initialState = {
|
||||
// Products list
|
||||
products: [],
|
||||
totalProducts: 0,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
|
||||
// Current product (for edit/view)
|
||||
currentProduct: null,
|
||||
|
||||
// Search results
|
||||
searchResults: [],
|
||||
searchQuery: '',
|
||||
|
||||
// Categories and brands
|
||||
categories: [],
|
||||
brands: [],
|
||||
|
||||
// Loading states
|
||||
loading: false,
|
||||
productLoading: false,
|
||||
searchLoading: false,
|
||||
|
||||
// Error states
|
||||
error: null,
|
||||
productError: null,
|
||||
searchError: null,
|
||||
|
||||
// Operation states
|
||||
creating: false,
|
||||
updating: false,
|
||||
deleting: false,
|
||||
};
|
||||
|
||||
const productReducer = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
// Fetch Products
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCTS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCTS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
products: action.payload.data || action.payload,
|
||||
totalProducts: action.payload.total || action.payload.length,
|
||||
currentPage: action.payload.currentPage || 1,
|
||||
totalPages: action.payload.totalPages || 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCTS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Fetch Single Product
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCT_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
productLoading: true,
|
||||
productError: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCT_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
productLoading: false,
|
||||
currentProduct: action.payload,
|
||||
productError: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.FETCH_PRODUCT_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
productLoading: false,
|
||||
productError: action.payload,
|
||||
};
|
||||
|
||||
// Create Product
|
||||
case PRODUCT_ACTIONS.CREATE_PRODUCT_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
creating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.CREATE_PRODUCT_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
products: [action.payload, ...state.products],
|
||||
totalProducts: state.totalProducts + 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.CREATE_PRODUCT_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
creating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Update Product
|
||||
case PRODUCT_ACTIONS.UPDATE_PRODUCT_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
updating: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.UPDATE_PRODUCT_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
products: state.products.map(product =>
|
||||
product.id === action.payload.id ? action.payload.data : product
|
||||
),
|
||||
currentProduct: action.payload.data,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.UPDATE_PRODUCT_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
updating: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Delete Product
|
||||
case PRODUCT_ACTIONS.DELETE_PRODUCT_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
deleting: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.DELETE_PRODUCT_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
products: state.products.filter(product => product.id !== action.payload),
|
||||
totalProducts: state.totalProducts - 1,
|
||||
error: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.DELETE_PRODUCT_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
deleting: false,
|
||||
error: action.payload,
|
||||
};
|
||||
|
||||
// Search Products
|
||||
case PRODUCT_ACTIONS.SEARCH_PRODUCTS_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: true,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.SEARCH_PRODUCTS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchResults: action.payload.data || action.payload,
|
||||
searchQuery: action.payload.query || '',
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.SEARCH_PRODUCTS_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
searchLoading: false,
|
||||
searchError: action.payload,
|
||||
};
|
||||
|
||||
// Categories and Brands
|
||||
case PRODUCT_ACTIONS.FETCH_CATEGORIES_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
categories: action.payload,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.FETCH_BRANDS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
brands: action.payload,
|
||||
};
|
||||
|
||||
// Clear States
|
||||
case PRODUCT_ACTIONS.CLEAR_PRODUCT_ERROR:
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
productError: null,
|
||||
searchError: null,
|
||||
};
|
||||
|
||||
case PRODUCT_ACTIONS.CLEAR_CURRENT_PRODUCT:
|
||||
return {
|
||||
...state,
|
||||
currentProduct: null,
|
||||
productError: null,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default productReducer;
|
||||
Reference in New Issue
Block a user