first commit
This commit is contained in:
0
src/features/analytics/analyticsAPI.js
Normal file
0
src/features/analytics/analyticsAPI.js
Normal file
0
src/features/analytics/analyticsSlice.js
Normal file
0
src/features/analytics/analyticsSlice.js
Normal file
0
src/features/auth/authAPI.js
Normal file
0
src/features/auth/authAPI.js
Normal file
64
src/features/auth/authSlice.js
Normal file
64
src/features/auth/authSlice.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
// import axios from "axios";
|
||||
import api from "../../app/api";
|
||||
|
||||
export const login = createAsyncThunk(
|
||||
"auth/login",
|
||||
async (credentials, thunkAPI) => {
|
||||
try {
|
||||
const response = await api.post("/auth/login", credentials);
|
||||
|
||||
console.log("Login response:", response.data);
|
||||
|
||||
const token = response.data?.token || response.data?.data?.token;
|
||||
const user = response.data?.user || response.data?.data?.user;
|
||||
|
||||
if (!token) {
|
||||
throw new Error("No token returned from server");
|
||||
}
|
||||
|
||||
localStorage.setItem("token", token);
|
||||
return { user, token };
|
||||
} catch (error) {
|
||||
return thunkAPI.rejectWithValue(
|
||||
error.response?.data?.message || "Login failed"
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState: {
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
token: localStorage.getItem("token") || null,
|
||||
},
|
||||
reducers: {
|
||||
logout: (state) => {
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
localStorage.removeItem("token");
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(login.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(login.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.user = action.payload.user;
|
||||
state.token = action.payload.token;
|
||||
})
|
||||
.addCase(login.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { logout } = authSlice.actions;
|
||||
export default authSlice.reducer;
|
||||
0
src/features/auth/authUtils.js
Normal file
0
src/features/auth/authUtils.js
Normal file
81
src/features/categories/categoryAPI.js
Normal file
81
src/features/categories/categoryAPI.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// src/features/categories/categoryAPI.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const categoryApi = createApi({
|
||||
reducerPath: "categoryApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:3000/api",
|
||||
prepareHeaders: (headers, { getState }) => {
|
||||
const token = getState().auth?.token;
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
tagTypes: ["Categories"],
|
||||
endpoints: (builder) => ({
|
||||
getCategoryTree: builder.query({
|
||||
query: () => "/admin/tree",
|
||||
providesTags: ["Categories"],
|
||||
}),
|
||||
|
||||
addCategory: builder.mutation({
|
||||
query: (body) => ({
|
||||
url: "/admin/categories",
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: ["Categories"],
|
||||
}),
|
||||
|
||||
updateCategory: builder.mutation({
|
||||
query: ({ id, ...body }) => ({
|
||||
url: `/admin/categories/${id}`,
|
||||
method: "PUT",
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: ["Categories"],
|
||||
}),
|
||||
|
||||
getCategoryById: builder.query({
|
||||
query: (id) => `/admin/categories/${id}`,
|
||||
}),
|
||||
|
||||
updateCategoryStatus: builder.mutation({
|
||||
query: ({ id, isActive }) => ({
|
||||
url: `/admin/categories/${id}/status`,
|
||||
method: "PATCH",
|
||||
body: { isActive },
|
||||
}),
|
||||
invalidatesTags: ["Categories"],
|
||||
}),
|
||||
|
||||
deleteCategory: builder.mutation({
|
||||
query: (id) => ({
|
||||
url: `/admin/categories/${id}`,
|
||||
method: "DELETE",
|
||||
}),
|
||||
invalidatesTags: ["Categories"],
|
||||
}),
|
||||
|
||||
reorderCategories: builder.mutation({
|
||||
query: (orders) => ({
|
||||
url: "/admin/categories/reorder",
|
||||
method: "PATCH",
|
||||
body: { orders },
|
||||
}),
|
||||
invalidatesTags: ["Categories"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetCategoryTreeQuery,
|
||||
useAddCategoryMutation,
|
||||
useUpdateCategoryMutation,
|
||||
useDeleteCategoryMutation,
|
||||
useGetCategoryByIdQuery,
|
||||
useUpdateCategoryStatusMutation,
|
||||
useReorderCategoriesMutation,
|
||||
} = categoryApi;
|
||||
|
||||
export default categoryApi;
|
||||
0
src/features/categories/categorySlice.js
Normal file
0
src/features/categories/categorySlice.js
Normal file
53
src/features/coupons/couponAPI.js
Normal file
53
src/features/coupons/couponAPI.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// src/features/coupons/couponAPI.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const couponApi = createApi({
|
||||
reducerPath: "couponApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:3000/api",
|
||||
prepareHeaders: (headers, { getState }) => {
|
||||
const token = getState().auth?.token;
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
tagTypes: ["Coupons"],
|
||||
endpoints: (builder) => ({
|
||||
getCoupons: builder.query({
|
||||
query: () => "/admin/coupons",
|
||||
transformResponse: (res) => res.data.coupons,
|
||||
providesTags: ["Coupons"],
|
||||
}),
|
||||
|
||||
// ✅ GET SINGLE COUPON (NEW)
|
||||
getCouponById: builder.query({
|
||||
query: (id) => `/admin/coupons/${id}`,
|
||||
transformResponse: (res) => res.data, // 🔥 IMPORTANT
|
||||
providesTags: ["Coupons"],
|
||||
}),
|
||||
|
||||
createCoupon: builder.mutation({
|
||||
query: (body) => ({
|
||||
url: "/admin/coupons",
|
||||
method: "POST",
|
||||
body,
|
||||
}),
|
||||
invalidatesTags: ["Coupons"],
|
||||
}),
|
||||
|
||||
toggleCouponStatus: builder.mutation({
|
||||
query: (id) => ({
|
||||
url: `/admin/coupons/${id}/toggle`,
|
||||
method: "PATCH",
|
||||
}),
|
||||
invalidatesTags: ["Coupons"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetCouponsQuery,
|
||||
useGetCouponByIdQuery,
|
||||
useCreateCouponMutation,
|
||||
useToggleCouponStatusMutation,
|
||||
} = couponApi;
|
||||
123
src/features/dashboard/dashboardSlice.js
Normal file
123
src/features/dashboard/dashboardSlice.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
// import axios from "../../app/api";
|
||||
|
||||
// // ✅ Async thunk to fetch dashboard stats
|
||||
// export const fetchDashboard = createAsyncThunk(
|
||||
// "dashboard/fetchDashboard",
|
||||
// async (_, { rejectWithValue }) => {
|
||||
// try {
|
||||
// const res = await axios.get("/admin/dashboard");
|
||||
// return res.data.data;
|
||||
// } catch (error) {
|
||||
// return rejectWithValue(error.response?.data || error.message);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
// const dashboardSlice = createSlice({
|
||||
// name: "dashboard",
|
||||
// initialState: {
|
||||
// stats: null,
|
||||
// loading: false,
|
||||
// error: null,
|
||||
// },
|
||||
// reducers: {},
|
||||
// extraReducers: (builder) => {
|
||||
// builder
|
||||
// .addCase(fetchDashboard.pending, (state) => {
|
||||
// state.loading = true;
|
||||
// state.error = null;
|
||||
// })
|
||||
// .addCase(fetchDashboard.fulfilled, (state, action) => {
|
||||
// state.loading = false;
|
||||
// state.stats = action.payload;
|
||||
// })
|
||||
// .addCase(fetchDashboard.rejected, (state, action) => {
|
||||
// state.loading = false;
|
||||
// state.error = action.payload;
|
||||
// });
|
||||
// },
|
||||
// });
|
||||
|
||||
// export default dashboardSlice.reducer;
|
||||
|
||||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import axios from "../../app/api";
|
||||
|
||||
/* =====================================================
|
||||
1️⃣ Fetch Main Dashboard Data
|
||||
Endpoint: GET /admin/dashboard
|
||||
===================================================== */
|
||||
export const fetchDashboard = createAsyncThunk(
|
||||
"dashboard/fetchDashboard",
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const res = await axios.get("/admin/dashboard");
|
||||
return res.data.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data?.message || error.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/* =====================================================
|
||||
2️⃣ Fetch Coupon Statistics
|
||||
Endpoint: GET /admin/stats
|
||||
===================================================== */
|
||||
export const fetchCouponStats = createAsyncThunk(
|
||||
"dashboard/fetchCouponStats",
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const res = await axios.get("/admin/stats");
|
||||
return res.data.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.response?.data?.message || error.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/* =====================================================
|
||||
Dashboard Slice
|
||||
===================================================== */
|
||||
const dashboardSlice = createSlice({
|
||||
name: "dashboard",
|
||||
initialState: {
|
||||
stats: null, // data from /admin/dashboard
|
||||
couponStats: null, // data from /admin/stats
|
||||
loading: false,
|
||||
error: null,
|
||||
},
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
|
||||
/* ===== DASHBOARD DATA ===== */
|
||||
.addCase(fetchDashboard.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.error = null;
|
||||
})
|
||||
.addCase(fetchDashboard.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.stats = action.payload;
|
||||
})
|
||||
.addCase(fetchDashboard.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
})
|
||||
|
||||
/* ===== COUPON STATS ===== */
|
||||
.addCase(fetchCouponStats.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(fetchCouponStats.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.couponStats = action.payload;
|
||||
})
|
||||
.addCase(fetchCouponStats.rejected, (state, action) => {
|
||||
state.loading = false;
|
||||
state.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default dashboardSlice.reducer;
|
||||
107
src/features/orders/ordersAPI.js
Normal file
107
src/features/orders/ordersAPI.js
Normal file
@@ -0,0 +1,107 @@
|
||||
// src/features/orders/orderApi.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const orderApi = createApi({
|
||||
reducerPath: "orderApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL,
|
||||
prepareHeaders: (headers, { getState }) => {
|
||||
const token = getState().auth?.token;
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
getOrders: builder.query({
|
||||
query: ({ page = 1, limit = 10 }) =>
|
||||
`/admin/orders?page=${page}&limit=${limit}`,
|
||||
providesTags: ["Orders"],
|
||||
}),
|
||||
|
||||
// ✅ ADD THIS
|
||||
getOrderById: builder.query({
|
||||
query: (id) => `/admin/orders/${id}`,
|
||||
providesTags: ["Orders"],
|
||||
}),
|
||||
|
||||
// =====================================================
|
||||
// UPDATE ORDER STATUS (Delivery Admin)
|
||||
// Used in: Order Details Page (Update to SHIPPED / DELIVERED etc.)
|
||||
// =====================================================
|
||||
updateOrderStatus: builder.mutation({
|
||||
query: ({ id, status, trackingNumber }) => ({
|
||||
url: `/delivery/admin/${id}/status`,
|
||||
method: "PUT",
|
||||
body: {
|
||||
status,
|
||||
trackingNumber,
|
||||
},
|
||||
}),
|
||||
invalidatesTags: ["Orders"], // better
|
||||
}),
|
||||
|
||||
getOrderHistory: builder.query({
|
||||
query: (id) => `/admin/${id}/history`,
|
||||
}),
|
||||
|
||||
// REQUESTED APPROVED REJECTEDCOMPLETED
|
||||
|
||||
// =====================================================
|
||||
// RETURN REQUESTS (Admin - Pending / Approved / Rejected)
|
||||
// Used in: Admin → Returns → Requests List
|
||||
// Status: REQUESTED | APPROVED | REJECTED
|
||||
// =====================================================
|
||||
getReturnRequests: builder.query({
|
||||
query: ({ page = 1, limit = 10 }) =>
|
||||
`/orders/admin/returns?page=${page}&limit=${limit}`,
|
||||
providesTags: ["Returns"],
|
||||
}),
|
||||
|
||||
// =====================================================
|
||||
// RETURN HISTORY (Admin - Completed / All Return Records)
|
||||
// Used in: Admin → Returns → History Page
|
||||
// =====================================================
|
||||
|
||||
getReturnedOrders: builder.query({
|
||||
query: ({ page = 1, limit = 10 }) =>
|
||||
`/orders/admin/returns/list?page=${page}&limit=${limit}`,
|
||||
providesTags: ["Returns"],
|
||||
}),
|
||||
|
||||
// =====================================================
|
||||
// RETURN DETAILS (Single Return Order)
|
||||
// Used in: Admin → Returns → View Details Page
|
||||
// URL: /returns/:id
|
||||
// =====================================================
|
||||
|
||||
getReturnRequestById: builder.query({
|
||||
query: (id) => `/orders/admin/returns/${id}`,
|
||||
providesTags: (result, error, id) => [{ type: "Returns", id }],
|
||||
}),
|
||||
|
||||
// =====================================================
|
||||
// UPDATE RETURN STATUS
|
||||
// Used in: ReturnDetails page (Approve / Reject buttons)
|
||||
// action = "APPROVE" | "REJECT"
|
||||
// =====================================================
|
||||
updateReturnStatus: builder.mutation({
|
||||
query: ({ id, action }) => ({
|
||||
url: `/${id}/return/status`,
|
||||
method: "PUT",
|
||||
body: { action }, // action = "APPROVE" | "REJECT"
|
||||
}),
|
||||
invalidatesTags: ["Returns"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const {
|
||||
useGetOrdersQuery,
|
||||
useGetOrderByIdQuery,
|
||||
useGetReturnRequestsQuery,
|
||||
useGetReturnedOrdersQuery,
|
||||
useGetReturnRequestByIdQuery,
|
||||
useUpdateReturnStatusMutation,
|
||||
useUpdateOrderStatusMutation,
|
||||
useGetOrderHistoryQuery,
|
||||
} = orderApi;
|
||||
0
src/features/orders/ordersSlice.js
Normal file
0
src/features/orders/ordersSlice.js
Normal file
23
src/features/products/productAPI.js
Normal file
23
src/features/products/productAPI.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
const productApi = createApi({
|
||||
reducerPath: "productApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:5000/api",
|
||||
prepareHeaders: (headers, { getState }) => {
|
||||
const token = getState().auth?.token;
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
tagTypes: ["Products"],
|
||||
endpoints: (builder) => ({
|
||||
getProducts: builder.query({
|
||||
query: ({ page = 1, limit = 10 } = {}) => `/admin/products?page=${page}&limit=${limit}`,
|
||||
providesTags: ["Products"],
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetProductsQuery } = productApi;
|
||||
export default productApi;
|
||||
0
src/features/products/productSlice.js
Normal file
0
src/features/products/productSlice.js
Normal file
0
src/features/products/productUtils.js
Normal file
0
src/features/products/productUtils.js
Normal file
21
src/features/report/customersAPI.js
Normal file
21
src/features/report/customersAPI.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// src/features/reports/customersAPI.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const customersApi = createApi({
|
||||
reducerPath: "customersApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:5000/api",
|
||||
prepareHeaders: (headers) => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
getCustomersReport: builder.query({
|
||||
query: () => "/admin/reports/customers",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetCustomersReportQuery } = customersApi;
|
||||
21
src/features/report/inventoryAPI.js
Normal file
21
src/features/report/inventoryAPI.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// src/features/reports/inventoryAPI.js
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const inventoryApi = createApi({
|
||||
reducerPath: "inventoryApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:5000/api",
|
||||
prepareHeaders: (headers) => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
getInventoryStats: builder.query({
|
||||
query: () => "/admin/reports/inventory",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetInventoryStatsQuery } = inventoryApi;
|
||||
22
src/features/report/ordersAPI.js
Normal file
22
src/features/report/ordersAPI.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const ordersApi = createApi({
|
||||
reducerPath: "ordersApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:5000/api",
|
||||
prepareHeaders: (headers) => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
headers.set("Authorization", `Bearer ${token}`);
|
||||
}
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
getOrdersReport: builder.query({
|
||||
query: () => "/admin/reports/orders",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetOrdersReportQuery } = ordersApi;
|
||||
20
src/features/report/salesAPI.js
Normal file
20
src/features/report/salesAPI.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
|
||||
|
||||
export const salesApi = createApi({
|
||||
reducerPath: "salesApi",
|
||||
baseQuery: fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL || "http://localhost:5000/api",
|
||||
prepareHeaders: (headers) => {
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
endpoints: (builder) => ({
|
||||
getSalesReport: builder.query({
|
||||
query: () => "/admin/reports/sales",
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetSalesReportQuery } = salesApi;
|
||||
0
src/features/users/userAPI.js
Normal file
0
src/features/users/userAPI.js
Normal file
0
src/features/users/userSlice.js
Normal file
0
src/features/users/userSlice.js
Normal file
Reference in New Issue
Block a user