Initial commit
This commit is contained in:
105
src/features/auth/authSlice.ts
Normal file
105
src/features/auth/authSlice.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { baseApi } from "redux/api/apiSlice";
|
||||
import { AppThunk } from "redux/store";
|
||||
import { ACCESS_TOKEN, USER_NAME } from "utils/constants";
|
||||
import { LoginResponseType, UserType } from "utils/types/appTypes";
|
||||
|
||||
export type AuthState = {
|
||||
user: UserType | undefined;
|
||||
isActivated: boolean;
|
||||
loading: boolean;
|
||||
loginFailed: boolean;
|
||||
expiredToken: boolean;
|
||||
error: string;
|
||||
token: string;
|
||||
loaded: boolean;
|
||||
};
|
||||
|
||||
const initialState: AuthState = {
|
||||
user: undefined,
|
||||
isActivated: !!localStorage.getItem(ACCESS_TOKEN),
|
||||
loaded: false,
|
||||
loading: false,
|
||||
loginFailed: false,
|
||||
expiredToken: false,
|
||||
error: "",
|
||||
token: localStorage.getItem(ACCESS_TOKEN) || "",
|
||||
};
|
||||
export const authSlice = createSlice({
|
||||
name: "auth",
|
||||
initialState,
|
||||
reducers: {
|
||||
resetState: () => initialState,
|
||||
initialLoadStart(state) {
|
||||
state.loading = true;
|
||||
state.isActivated = false;
|
||||
},
|
||||
initialLoadSuccess(state) {
|
||||
state.loading = false;
|
||||
state.isActivated = true;
|
||||
},
|
||||
loginStart(state) {
|
||||
state.loading = true;
|
||||
state.loginFailed = false;
|
||||
state.loaded = false;
|
||||
},
|
||||
loginSuccess(state, action: PayloadAction<LoginResponseType>) {
|
||||
if (action.payload.token) {
|
||||
localStorage.setItem(ACCESS_TOKEN, action.payload.token);
|
||||
state.token = action.payload.token;
|
||||
if (state.user?.username)
|
||||
localStorage.setItem(USER_NAME, state.user.username);
|
||||
|
||||
state.isActivated = true;
|
||||
state.loading = false;
|
||||
state.loginFailed = false;
|
||||
state.expiredToken = false;
|
||||
state.error = "";
|
||||
state.loaded = true;
|
||||
}
|
||||
},
|
||||
logout: (state) => {
|
||||
localStorage.removeItem(ACCESS_TOKEN);
|
||||
state.isActivated = false;
|
||||
state.token = "";
|
||||
state.user = undefined;
|
||||
},
|
||||
failedWithError(state, action: PayloadAction<string>) {
|
||||
state.loading = false;
|
||||
state.loginFailed = true;
|
||||
state.error = action.payload;
|
||||
},
|
||||
resetLoadedStatus(state) {
|
||||
state.loaded = false;
|
||||
},
|
||||
setUser(state, action: PayloadAction<any>) {
|
||||
state.user = action.payload;
|
||||
},
|
||||
tokenExpired(state) {
|
||||
state.expiredToken = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
resetState,
|
||||
initialLoadStart,
|
||||
initialLoadSuccess,
|
||||
loginStart,
|
||||
loginSuccess,
|
||||
failedWithError,
|
||||
tokenExpired,
|
||||
logout,
|
||||
setUser,
|
||||
resetLoadedStatus,
|
||||
} = authSlice.actions;
|
||||
|
||||
export default authSlice.reducer;
|
||||
|
||||
export const logoutThunk = (): AppThunk => (dispatch) => {
|
||||
if (localStorage.getItem(ACCESS_TOKEN)) {
|
||||
localStorage.clear();
|
||||
dispatch(baseApi.util.resetApiState());
|
||||
dispatch(logout());
|
||||
}
|
||||
};
|
||||
22
src/features/error/appErrorSlice.ts
Normal file
22
src/features/error/appErrorSlice.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
export type AppErrorState = {
|
||||
errorCode: number;
|
||||
};
|
||||
|
||||
const initialState: AppErrorState = {
|
||||
errorCode: 200,
|
||||
};
|
||||
|
||||
export const appErrorSlice = createSlice({
|
||||
name: "appError",
|
||||
initialState,
|
||||
reducers: {
|
||||
setErrorCode(state, action: PayloadAction<number>) {
|
||||
state.errorCode = action.payload;
|
||||
// if(errorCode === 401 && store.getState().auth.isActivated)dispach()
|
||||
},
|
||||
},
|
||||
});
|
||||
export const { setErrorCode } = appErrorSlice.actions;
|
||||
export default appErrorSlice.reducer;
|
||||
63
src/features/locale/localeSlice.ts
Normal file
63
src/features/locale/localeSlice.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/ar";
|
||||
import LocalizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import preParsePostFormat from "dayjs/plugin/preParsePostFormat";
|
||||
import { AppThunk } from "redux/store";
|
||||
import { DEFAULT_LANGUAGE } from "utils/constants";
|
||||
|
||||
dayjs.extend(LocalizedFormat);
|
||||
dayjs.extend(preParsePostFormat); // for arabic numbers
|
||||
|
||||
export type LocaleState = {
|
||||
locale: string;
|
||||
isRTL: boolean;
|
||||
};
|
||||
|
||||
const initialState: LocaleState = {
|
||||
locale:
|
||||
localStorage.getItem(DEFAULT_LANGUAGE) ||
|
||||
import.meta.env.VITE_DEFAULT_LANGUAGE,
|
||||
isRTL:
|
||||
(localStorage.getItem(DEFAULT_LANGUAGE) ||
|
||||
import.meta.env.VITE_DEFAULT_LANGUAGE) === "ar",
|
||||
};
|
||||
|
||||
function setDateLocale(appLocale: string) {
|
||||
switch (appLocale) {
|
||||
case "ar":
|
||||
dayjs.locale("ar");
|
||||
break;
|
||||
case "en":
|
||||
dayjs.locale("en");
|
||||
break;
|
||||
default:
|
||||
dayjs.locale("en");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export const localeSlice = createSlice({
|
||||
name: "locale",
|
||||
initialState,
|
||||
reducers: {
|
||||
setLocale(state, action: PayloadAction<string>) {
|
||||
state.locale = action.payload;
|
||||
state.isRTL = action.payload === "ar";
|
||||
},
|
||||
},
|
||||
});
|
||||
export const { setLocale } = localeSlice.actions;
|
||||
export default localeSlice.reducer;
|
||||
|
||||
export const setLocalesThunk =
|
||||
(newLocale?: string): AppThunk =>
|
||||
async (dispatch) => {
|
||||
const storedLang = localStorage.getItem(DEFAULT_LANGUAGE);
|
||||
|
||||
const lang =
|
||||
newLocale || storedLang || import.meta.env.VITE_DEFAULT_LANGUAGE;
|
||||
if (storedLang !== lang) localStorage.setItem(DEFAULT_LANGUAGE, lang);
|
||||
setDateLocale(lang);
|
||||
dispatch(setLocale(lang));
|
||||
};
|
||||
108
src/features/order/helper.ts
Normal file
108
src/features/order/helper.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { TaxType } from "modules/system/taxes/types";
|
||||
import { ItemType, OrderType } from "pages/pos/orders/types";
|
||||
|
||||
/**
|
||||
* To aggregate all the items to make sure to show totals over all the order not only one order
|
||||
* @param activeTab
|
||||
* @param orders
|
||||
* @param items
|
||||
* @returns
|
||||
*/
|
||||
export function prepareTheCompletedList(
|
||||
activeTab: string,
|
||||
orders: OrderType[],
|
||||
items: ItemType[]
|
||||
) {
|
||||
let theCompletedList: ItemType[] | undefined = [];
|
||||
if (activeTab == "0")
|
||||
theCompletedList = [...(theCompletedList || []), ...(items || [])];
|
||||
|
||||
if (activeTab)
|
||||
orders.forEach((o) => {
|
||||
if (o.id == activeTab)
|
||||
theCompletedList = [...(theCompletedList || []), ...(items || [])];
|
||||
else theCompletedList = [...(theCompletedList || []), ...(o.items || [])];
|
||||
});
|
||||
else {
|
||||
theCompletedList = items;
|
||||
}
|
||||
return theCompletedList;
|
||||
}
|
||||
|
||||
export function calculateSubTotal(items: Array<ItemType>) {
|
||||
let subTotalAmount = 0;
|
||||
items?.forEach((i) => {
|
||||
subTotalAmount += Number(i.unit_price) * (i?.quantity || 0);
|
||||
});
|
||||
return subTotalAmount;
|
||||
}
|
||||
|
||||
export function calculateDiscount(
|
||||
subTotalAmount: number,
|
||||
discount: {
|
||||
type: "PERCENTAGE" | "FIXED";
|
||||
value: number;
|
||||
}
|
||||
) {
|
||||
let discountAmount = 0;
|
||||
if (discount.type === "PERCENTAGE") {
|
||||
discountAmount += (Number(discount.value) / 100) * subTotalAmount;
|
||||
}
|
||||
if (discount.type === "FIXED") {
|
||||
discountAmount += Number(discount.value);
|
||||
}
|
||||
return Math.ceil(discountAmount / 100) * 100;
|
||||
}
|
||||
|
||||
export function calculateTax(subTotalAmount: number, taxes: Array<TaxType>) {
|
||||
const newTaxes = [...taxes];
|
||||
let taxesAmount = 0;
|
||||
newTaxes
|
||||
.sort((a, b) => (a.from_tax?.id || 0) - (b.from_tax?.id || 0))
|
||||
.forEach((tax: TaxType) => {
|
||||
taxesAmount +=
|
||||
Math.ceil(calculateTaxAmount(subTotalAmount, tax) / 100) * 100;
|
||||
});
|
||||
|
||||
return taxesAmount;
|
||||
}
|
||||
|
||||
export function calculateSpecificTax(
|
||||
subTotalAmount: number,
|
||||
taxes: Array<TaxType>,
|
||||
tax: TaxType
|
||||
) {
|
||||
let taxesAmount = 0;
|
||||
|
||||
const newTaxes = [...taxes];
|
||||
newTaxes
|
||||
.sort((a, b) => (a.from_tax?.id || 0) - (b.from_tax?.id || 0))
|
||||
.forEach((t: TaxType) => {
|
||||
if (t.id === tax.id)
|
||||
taxesAmount +=
|
||||
Math.ceil(calculateTaxAmount(subTotalAmount, tax) / 100) * 100;
|
||||
});
|
||||
|
||||
return taxesAmount;
|
||||
}
|
||||
|
||||
export function calculateTaxAmount(amount: number, tax: TaxType) {
|
||||
const value = tax.value;
|
||||
let total = amount;
|
||||
|
||||
if (tax.from_tax !== null) {
|
||||
total = calculateTaxAmount(total, tax.from_tax);
|
||||
}
|
||||
|
||||
total = tax.type === "FIXED" ? value : (value * total) / 100;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
export function calculateGrandTotalAmount(
|
||||
subTotalAmount: number,
|
||||
taxAmount: number,
|
||||
discountAmount: number
|
||||
) {
|
||||
return subTotalAmount + taxAmount - discountAmount;
|
||||
}
|
||||
311
src/features/order/orderSlice.ts
Normal file
311
src/features/order/orderSlice.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { RootState } from "redux/store";
|
||||
import { CartItem } from "utils/types/appTypes";
|
||||
|
||||
interface LocationData {
|
||||
lat: number;
|
||||
lng: number;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface RoomDetailsType {
|
||||
roomNo: string;
|
||||
floorNo: string;
|
||||
guestName: string;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export interface OfficeDetailsType {
|
||||
officeNo: string;
|
||||
floorNo: string;
|
||||
note: string;
|
||||
company: string;
|
||||
contactPerson: string;
|
||||
phone: string;
|
||||
}
|
||||
|
||||
export interface GiftDetailsType {
|
||||
receiverName: string;
|
||||
receiverPhone: string;
|
||||
message: string;
|
||||
senderName: string;
|
||||
senderPhone: string;
|
||||
isSecret: boolean;
|
||||
}
|
||||
|
||||
interface CartState {
|
||||
items: CartItem[];
|
||||
tmp: unknown;
|
||||
specialRequest: string;
|
||||
location: LocationData | null;
|
||||
roomDetails: RoomDetailsType | null;
|
||||
officeDetails: OfficeDetailsType | null;
|
||||
giftDetails: GiftDetailsType | null;
|
||||
coupon: string;
|
||||
tip: string;
|
||||
tables: string[];
|
||||
estimateTime: Date;
|
||||
estimateTimeDate: Date;
|
||||
estimateTimeTime: string;
|
||||
collectionMethod: string;
|
||||
}
|
||||
|
||||
// localStorage keys
|
||||
const CART_STORAGE_KEYS = {
|
||||
ITEMS: 'fascano_cart_items',
|
||||
SPECIAL_REQUEST: 'fascano_special_request',
|
||||
COUPON: 'fascano_coupon',
|
||||
TIP: 'fascano_tip',
|
||||
TABLES: 'fascano_tables',
|
||||
LOCATION: 'fascano_location',
|
||||
ROOM_DETAILS: 'fascano_room_details',
|
||||
OFFICE_DETAILS: 'fascano_office_details',
|
||||
GIFT_DETAILS: 'fascano_gift_details',
|
||||
ESTIMATE_TIME: 'fascano_estimate_time',
|
||||
ESTIMATE_TIME_DATE: 'fascano_estimate_time_date',
|
||||
ESTIMATE_TIME_TIME: 'fascano_estimate_time_time',
|
||||
COLLECTION_METHOD: 'fascano_collection_method',
|
||||
} as const;
|
||||
|
||||
// Utility functions for localStorage
|
||||
const getFromLocalStorage = <T>(key: string, defaultValue: T): T => {
|
||||
if (typeof window === 'undefined') return defaultValue;
|
||||
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : defaultValue;
|
||||
} catch (error) {
|
||||
console.warn(`Error reading ${key} from localStorage:`, error);
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
const initialState: CartState = {
|
||||
items: getFromLocalStorage(CART_STORAGE_KEYS.ITEMS, []),
|
||||
tmp: null,
|
||||
specialRequest: getFromLocalStorage(CART_STORAGE_KEYS.SPECIAL_REQUEST, ""),
|
||||
location: getFromLocalStorage(CART_STORAGE_KEYS.LOCATION, null),
|
||||
roomDetails: getFromLocalStorage(CART_STORAGE_KEYS.ROOM_DETAILS, null),
|
||||
officeDetails: getFromLocalStorage(CART_STORAGE_KEYS.OFFICE_DETAILS, null),
|
||||
giftDetails: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_DETAILS, null),
|
||||
coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""),
|
||||
tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""),
|
||||
tables: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, []),
|
||||
estimateTime: new Date(getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME, new Date().toISOString())),
|
||||
estimateTimeDate: new Date(getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_DATE, new Date().toISOString())),
|
||||
estimateTimeTime: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, ""),
|
||||
collectionMethod: getFromLocalStorage(CART_STORAGE_KEYS.COLLECTION_METHOD, ""),
|
||||
};
|
||||
|
||||
const orderSlice = createSlice({
|
||||
name: "order",
|
||||
initialState,
|
||||
reducers: {
|
||||
reset() {
|
||||
return initialState;
|
||||
},
|
||||
addItem(state, action: PayloadAction<{ item: Omit<CartItem, "quantity">; quantity: number }>) {
|
||||
const { item, quantity } = action.payload;
|
||||
const existingItem = state.items.find((i) => i.id === item.id);
|
||||
|
||||
if (existingItem) {
|
||||
state.items = state.items.map((i) =>
|
||||
i.id === item.id ? { ...i, quantity: i.quantity + quantity } : i
|
||||
);
|
||||
} else {
|
||||
state.items = [...state.items, { ...item, quantity }];
|
||||
}
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items));
|
||||
}
|
||||
},
|
||||
updateQuantity(state, action: PayloadAction<{ id: number | string; quantity: number }>) {
|
||||
const { id, quantity } = action.payload;
|
||||
state.items = state.items.map((item) =>
|
||||
item.id === id ? { ...item, quantity } : item
|
||||
);
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items));
|
||||
}
|
||||
},
|
||||
removeItem(state, action: PayloadAction<number | string>) {
|
||||
state.items = state.items.filter((item) => item.id !== action.payload);
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ITEMS, JSON.stringify(state.items));
|
||||
}
|
||||
},
|
||||
clearCart(state) {
|
||||
state.items = [];
|
||||
state.specialRequest = "";
|
||||
state.coupon = "";
|
||||
state.tip = "";
|
||||
state.tables = [];
|
||||
state.location = null;
|
||||
state.roomDetails = null;
|
||||
state.officeDetails = null;
|
||||
state.giftDetails = null;
|
||||
state.estimateTime = new Date();
|
||||
state.estimateTimeDate = new Date();
|
||||
state.estimateTimeTime = "";
|
||||
state.collectionMethod = "";
|
||||
|
||||
// Clear all cart data from localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
Object.values(CART_STORAGE_KEYS).forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
}
|
||||
},
|
||||
updateSpecialRequest(state, action: PayloadAction<string>) {
|
||||
state.specialRequest = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.SPECIAL_REQUEST, JSON.stringify(state.specialRequest));
|
||||
}
|
||||
},
|
||||
clearSpecialRequest(state) {
|
||||
state.specialRequest = "";
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem(CART_STORAGE_KEYS.SPECIAL_REQUEST);
|
||||
}
|
||||
},
|
||||
updateCoupon(state, action: PayloadAction<string>) {
|
||||
state.coupon = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.COUPON, JSON.stringify(state.coupon));
|
||||
}
|
||||
},
|
||||
updateTip(state, action: PayloadAction<string>) {
|
||||
state.tip = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip));
|
||||
}
|
||||
},
|
||||
updateTables(state, action: PayloadAction<string[]>) {
|
||||
state.tables = [...state.tables, ...action.payload];
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.TABLES, JSON.stringify(state.tables));
|
||||
}
|
||||
},
|
||||
removeTable(state) {
|
||||
state.tables = [];
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem(CART_STORAGE_KEYS.TABLES);
|
||||
}
|
||||
},
|
||||
setTmp(state, action: PayloadAction<unknown>) {
|
||||
state.tmp = action.payload;
|
||||
},
|
||||
updateLocation(state, action: PayloadAction<LocationData | null>) {
|
||||
state.location = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.LOCATION, JSON.stringify(state.location));
|
||||
}
|
||||
},
|
||||
clearLocation(state) {
|
||||
state.location = null;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem(CART_STORAGE_KEYS.LOCATION);
|
||||
}
|
||||
},
|
||||
updateRoomDetails(state, action: PayloadAction<RoomDetailsType | null>) {
|
||||
state.roomDetails = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ROOM_DETAILS, JSON.stringify(state.roomDetails));
|
||||
}
|
||||
},
|
||||
updateOfficeDetails(state, action: PayloadAction<OfficeDetailsType | null>) {
|
||||
state.officeDetails = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.OFFICE_DETAILS, JSON.stringify(state.officeDetails));
|
||||
}
|
||||
},
|
||||
updateGiftDetails(state, action: PayloadAction<GiftDetailsType | null>) {
|
||||
state.giftDetails = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.GIFT_DETAILS, JSON.stringify(state.giftDetails));
|
||||
}
|
||||
},
|
||||
updateEstimateTime(state, action: PayloadAction<{ date: Date; time: string }>) {
|
||||
state.estimateTime = action.payload.date;
|
||||
state.estimateTimeDate = action.payload.date;
|
||||
state.estimateTimeTime = action.payload.time;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME, JSON.stringify(state.estimateTime.toISOString()));
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME_DATE, JSON.stringify(state.estimateTimeDate.toISOString()));
|
||||
localStorage.setItem(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, JSON.stringify(state.estimateTimeTime));
|
||||
}
|
||||
},
|
||||
updateCollectionMethod(state, action: PayloadAction<string>) {
|
||||
state.collectionMethod = action.payload;
|
||||
|
||||
// Sync to localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(CART_STORAGE_KEYS.COLLECTION_METHOD, JSON.stringify(state.collectionMethod));
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
addItem,
|
||||
updateQuantity,
|
||||
removeItem,
|
||||
clearCart,
|
||||
updateSpecialRequest,
|
||||
clearSpecialRequest,
|
||||
updateCoupon,
|
||||
updateTip,
|
||||
updateTables,
|
||||
removeTable,
|
||||
setTmp,
|
||||
updateLocation,
|
||||
clearLocation,
|
||||
updateRoomDetails,
|
||||
updateOfficeDetails,
|
||||
updateGiftDetails,
|
||||
updateEstimateTime,
|
||||
updateCollectionMethod,
|
||||
reset,
|
||||
} = orderSlice.actions;
|
||||
|
||||
// Selectors
|
||||
export const selectCart = (state: RootState) => state.order;
|
||||
export const selectCartItems = (state: RootState) => state.order.items;
|
||||
export const selectCartTotal = (state: RootState) =>
|
||||
state.order.items.reduce((total, item) => total + item.price * item.quantity, 0);
|
||||
export const selectCartItemsQuantity = (id: number | string) => (state: RootState) => {
|
||||
const item = state.order.items.find((i) => i.id === id);
|
||||
return item ? item.quantity : 0;
|
||||
};
|
||||
|
||||
export default orderSlice.reducer;
|
||||
62
src/features/theme/themeSlice.ts
Normal file
62
src/features/theme/themeSlice.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { RootState } from "redux/store";
|
||||
|
||||
interface ThemeState {
|
||||
themeName: string;
|
||||
}
|
||||
|
||||
// Theme persistence utilities
|
||||
const THEME_STORAGE_KEY = "theme";
|
||||
|
||||
const getInitialTheme = (): string => {
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
// Validate theme value
|
||||
if (savedTheme === "light" || savedTheme === "dark") {
|
||||
return savedTheme;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to read theme from localStorage:", error);
|
||||
}
|
||||
}
|
||||
return "light";
|
||||
};
|
||||
|
||||
const saveThemeToStorage = (theme: string): void => {
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
localStorage.setItem(THEME_STORAGE_KEY, theme);
|
||||
} catch (error) {
|
||||
console.warn("Failed to save theme to localStorage:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const initialState: ThemeState = {
|
||||
themeName: getInitialTheme(),
|
||||
};
|
||||
|
||||
const themeSlice = createSlice({
|
||||
name: "theme",
|
||||
initialState,
|
||||
reducers: {
|
||||
toggleTheme: (state: ThemeState) => {
|
||||
state.themeName = state.themeName === "light" ? "dark" : "light";
|
||||
saveThemeToStorage(state.themeName);
|
||||
},
|
||||
setTheme: (state: ThemeState, action: { payload: string }) => {
|
||||
// Validate theme value
|
||||
if (action.payload === "light" || action.payload === "dark") {
|
||||
state.themeName = action.payload;
|
||||
saveThemeToStorage(state.themeName);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { toggleTheme, setTheme } = themeSlice.actions;
|
||||
|
||||
export const selectTheme = (state: RootState) => state.theme;
|
||||
|
||||
export default themeSlice.reducer;
|
||||
Reference in New Issue
Block a user