Skip to content

Commit

Permalink
Merge pull request #945 from eisbuk/fix/bookings-deadline
Browse files Browse the repository at this point in the history
Fix/bookings deadline
  • Loading branch information
silviot authored Mar 23, 2024
2 parents d381cb5 + add41bf commit c3f96a4
Show file tree
Hide file tree
Showing 29 changed files with 1,092 additions and 543 deletions.
573 changes: 307 additions & 266 deletions common/autoinstallers/lint-staged/pnpm-lock.yaml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions packages/client/src/AppContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import Deleted from "@/pages/deleted";

import { getIsAdmin } from "@/store/selectors/auth";

import useSystemDate from "./hooks/useSystemDate";

/**
* All of the App content (including routes) wrapper.
* On change of auth credentials (and initial render)
Expand All @@ -57,6 +59,7 @@ const AppContent: React.FC = () => {

useFirestoreSubscribe(getOrganization(), subscribedCollections);
usePaginateFirestore();
useSystemDate();

// Remove the firestore emulator warning as it's in the way.
// We're adding a warning of our own, incorporated nicely into out layout
Expand Down
109 changes: 109 additions & 0 deletions packages/client/src/controllers/BookingDateDebugController.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useState, useEffect } from "react";
import { DateTime } from "luxon";
import { useDispatch, useSelector } from "react-redux";

import { BookingDateDebugDialog as DebugDialog } from "@eisbuk/ui";
import { updateLocalDocuments } from "@eisbuk/react-redux-firebase-firestore";
import { OrgSubCollection } from "@eisbuk/shared";

import { db } from "@/setup";

import { getOrganization } from "@/lib/getters";

import {
getBookingsCustomer,
getMonthDeadline,
} from "@/store/selectors/bookings";
import {
getCalendarDay,
getSecretKey,
getSystemDate,
} from "@/store/selectors/app";
import { resetSystemDate, setSystemDate } from "@/store/actions/appActions";

import {
FirestoreVariant,
doc,
getBookingsDocPath,
getDoc,
} from "@/utils/firestore";

const BookingDateDebugDialog: React.FC = () => {
const secretKey = useSelector(getSecretKey) || "";
const currentDate = useSelector(getCalendarDay);

const dispatch = useDispatch();

const systemDateValue = useSelector(getSystemDate).value;
const systemDate = useDate({
value: systemDateValue,
onChange: (date) => dispatch(setSystemDate(date)),
// Reset the system date on unmount, as it could have only been used (if it had been used at all) for debugging of
// the current page
onDestroy: () => dispatch(resetSystemDate()),
});

const customer = useSelector(getBookingsCustomer(secretKey));
const extendedDateValue = DateTime.fromISO(
customer?.extendedDate || getMonthDeadline(currentDate).toISODate()
);
// On extended date change, we're not persisting the change, merely updating the local (redux) state
const handleExtendedDateChange = (date: DateTime) => {
const data = { ...customer, extendedDate: date.toISODate() };
dispatch(
updateLocalDocuments(OrgSubCollection.Bookings, { [secretKey]: data })
);
};
const resetBookingsCustomer = async () => {
const firestore = FirestoreVariant.client({ instance: db });
const docRef = doc(
firestore,
getBookingsDocPath(getOrganization(), secretKey)
);
const data = await getDoc(docRef).then((d) => d.data()!);
dispatch(
updateLocalDocuments(OrgSubCollection.Bookings, { [secretKey]: data })
);
};
const extendedDate = useDate({
value: extendedDateValue,
onChange: handleExtendedDateChange,
onDestroy: resetBookingsCustomer,
});

return <DebugDialog systemDate={systemDate} extendedDate={extendedDate} />;
};

type UseDateParams = {
value: DateTime;
onChange: (date: DateTime) => void;
onDestroy?: () => void;
};

const useDate = ({ value, onChange, onDestroy }: UseDateParams) => {
useEffect(() => {
return onDestroy;
}, []);

const navigate = (days: -1 | 1) => () => onChange(value.plus({ days }));

const [localDate, setLocalDate] = useState(value.toISODate());
useEffect(() => {
setLocalDate(value.toISODate());
}, [value]);
const handleChange = (_date: string) => {
if (isIsoDate(_date)) {
// If date is a valid ISO string, update the DateTime value
// Local value is updated as side effect
onChange(DateTime.fromISO(_date));
}
// Update the local value
setLocalDate(_date);
};

return { value: localDate, navigate, onChange: handleChange };
};

const isIsoDate = (date: string): boolean => /^\d{4}-\d{2}-\d{2}$/.test(date);

export default BookingDateDebugDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import { createModal } from "@/features/modal/useModal";

import {
getBookingsCustomer,
getCountdownProps,
getMonthEmptyForBooking,
} from "@/store/selectors/bookings";
import { getCalendarDay, getSecretKey } from "@/store/selectors/app";

import useBookingsDeadlines from "@/hooks/useBookingsDeadline";

import {
getCalendarDay,
getSecretKey,
getSystemDate,
} from "@/store/selectors/app";

interface Props extends React.HTMLAttributes<HTMLElement> {
as?: keyof JSX.IntrinsicElements;
Expand All @@ -22,11 +28,23 @@ interface Props extends React.HTMLAttributes<HTMLElement> {
* rendering the component with appropriate state whilst providing a way to update the
* state to the store (for bookings finalisation).
*/
const BookingsCountdownContainer: React.FC<Props> = (props) => {
export const BookingsCountdownContainer: React.FC<Props> = (props) => {
const secretKey = useSelector(getSecretKey)!;

const currentDate = useSelector(getCalendarDay);
const countdownProps = useSelector(getCountdownProps(secretKey));
const { value: systemDate } = useSelector(getSystemDate);

const isMonthEmpty = useSelector(getMonthEmptyForBooking(secretKey));

const { month, isBookingAllowed, deadline, countdownVariant } =
useBookingsDeadlines();

const countdownProps = {
month,
deadline: isBookingAllowed ? deadline : null,
variant: countdownVariant,
};

const { id: customerId } = useSelector(getBookingsCustomer(secretKey)) || {};

const { openWithProps: openFinalizeBookingsDialog } =
Expand Down Expand Up @@ -58,6 +76,7 @@ const BookingsCountdownContainer: React.FC<Props> = (props) => {
<BookingsCountdown
{...props}
{...countdownProps}
systemDate={systemDate}
onFinalize={handleFinalize}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import { updateLocalDocuments } from "@eisbuk/react-redux-firebase-firestore";
import BookingsCountdownContainer from "../BookingsCountdownContainer";

import { getNewStore } from "@/store/createStore";
import { changeCalendarDate, storeSecretKey } from "@/store/actions/appActions";
import {
changeCalendarDate,
setSystemDate,
storeSecretKey,
} from "@/store/actions/appActions";

import { renderWithRedux } from "@/__testUtils__/wrappers";

Expand All @@ -35,8 +39,6 @@ describe("BookingsCountdown", () => {
test("should open a finalize bookings modal on 'Finalize' button click", () => {
// Set up test state so that the second deadline is shown
const testDate = DateTime.fromISO("2022-01-01");
// In order to keep tests consistent we need to also mock the `Date.now`
vi.spyOn(Date, "now").mockReturnValue(testDate.toMillis());
const month = testDate;
const extendedDate = testDate.plus({ days: 2 }).toISODate();
const store = getNewStore();
Expand All @@ -61,6 +63,8 @@ describe("BookingsCountdown", () => {
);
store.dispatch(changeCalendarDate(month));
store.dispatch(storeSecretKey(saul.secretKey));
// Instead of mocking Date.now, we're passing the test date (as system date) directly
store.dispatch(setSystemDate(testDate));
// With test state set up, 'finalize' button should be in the screen for
// provided 'month'
renderWithRedux(<BookingsCountdownContainer />, store);
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/enums/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export enum Action {
ChangeDay = "@@Eisbuk/CHANGE_DAY",
StoreSecretKey = "@@Eisbuk/STORE_SECRET_KEY",
RemoveSecretKey = "@@Eisbuk/REMOVE_SECRET_KEY",
SetSystemDate = "@@Eisbuk/SET_SYSTEM_DATE",
ResetSystemDate = "@@Eisbuk/RESET_SYSTEM_DATE",

SetSlotTime = "@@Eisbuk/SET_SLOT_TIME",

Expand Down
Loading

0 comments on commit c3f96a4

Please sign in to comment.