@qite/tide-booking-component
Version:
React Booking wizard & Booking product component for Tide
172 lines (146 loc) • 4.92 kB
text/typescript
import {
createAsyncThunk,
createSelector,
createSlice,
PayloadAction,
} from "@reduxjs/toolkit";
import { sum } from "lodash";
import { RootState } from "../../store";
import {
BookingPriceDetail,
BookingPriceDetails,
BookingProductNotification,
} from "@qite/tide-client/build/types";
import { setPackage } from "../booking/booking-slice";
import {
selectBookingPackageBookRequest,
selectLanguageCode,
selectPackageDetails,
} from "../booking/selectors";
import priceDetailsApi from "./price-details-api";
import { updatePackageDetails } from "./util";
export interface PriceDetailsState {
priceDetails: BookingPriceDetail[];
notifications: BookingProductNotification[];
deposit?: number;
isBusy: boolean;
}
export const fetchPriceDetails = createAsyncThunk(
"priceDetails/fetchPriceDetails",
async (_, { dispatch, getState, signal }) => {
dispatch(setFetchingPriceDetails(true));
dispatch(resetPriceDetails());
const state = getState() as RootState;
const languageCode = selectLanguageCode(state);
const request = selectBookingPackageBookRequest(state);
if (!request) {
throw Error("Product not available.");
}
const priceDetails: BookingPriceDetails =
await priceDetailsApi.fetchPriceDetails(
state.apiSettings,
request,
signal,
languageCode
);
dispatch(setFetchingPriceDetails(false));
const packageDetails = selectPackageDetails(state);
if (packageDetails) {
const updatedPackageDetails = updatePackageDetails(
packageDetails,
priceDetails.changedLines
);
dispatch(setPackage(updatedPackageDetails));
}
return priceDetails;
}
);
const priceDetailsSlice = createSlice({
name: "priceDetails",
initialState: <PriceDetailsState>{
priceDetails: [],
notifications: [],
isBusy: false,
},
reducers: {
setFetchingPriceDetails(state, action: PayloadAction<boolean>) {
state.isBusy = action.payload;
},
setNotifications(
state,
action: PayloadAction<BookingProductNotification[]>
) {
state.notifications = action.payload;
},
resetPriceDetails(state, _: PayloadAction<void>) {
state.priceDetails = [];
},
},
extraReducers: (builder) => {
builder.addCase(fetchPriceDetails.fulfilled, (state, action) => {
if (action.payload) {
state.deposit = action.payload.deposit;
state.priceDetails = action.payload.details;
state.notifications = action.payload.notifications;
}
});
},
});
export const selectPriceDetails = (state: RootState) =>
state.priceDetails.priceDetails;
export const selectPackagePriceDetails = createSelector(
selectPriceDetails,
(priceDetails) =>
priceDetails.filter((priceDetail) => priceDetail.isInPackage)
);
export const selectSeparatePackagePriceDetails = createSelector(
selectPackagePriceDetails,
(pricedetails) => {
const result: BookingPriceDetail[] = [];
const filteredPriceDetails = pricedetails.filter((priceDetail) => priceDetail.isSeparate)
filteredPriceDetails.forEach(priceDetail => {
const priceDetailToMerge = result.find(x => x.productCode === priceDetail.productCode && x.accommodationCode === priceDetail.accommodationCode && x.productType === priceDetail.productType);
if (priceDetailToMerge) {
priceDetailToMerge.total += priceDetail.total;
priceDetailToMerge.price += priceDetail.price;
} else {
result.push(Object.assign({}, priceDetail));
}
});
return result;
}
);
export const selectBasePrice = createSelector(
selectPackagePriceDetails,
(priceDetails) =>
sum(
priceDetails.map((priceDetail) => priceDetail.price * priceDetail.amount)
)
);
export const selectSeparateExtraPriceDetails = createSelector(
selectPriceDetails,
(priceDetails) =>
priceDetails.filter(
(priceDetail) => !priceDetail.isInPackage && priceDetail.isSeparate
)
);
export const selectTotalPrice = createSelector(
selectBasePrice,
selectSeparateExtraPriceDetails,
(basePrice, separatePriceDetails) =>
sum([
basePrice,
...separatePriceDetails.map(
(priceDetail) => priceDetail.price * priceDetail.amount
),
])
);
export const selectDeposit = (state: RootState) => state.priceDetails.deposit;
export const selectIsFetchingPriceDetails = (state: RootState) =>
state.priceDetails.isBusy;
export const selectNotifications = (state: RootState) =>
state.priceDetails.notifications;
const { setFetchingPriceDetails } = priceDetailsSlice.actions;
export const { resetPriceDetails, setNotifications } =
priceDetailsSlice.actions;
export default priceDetailsSlice.reducer;