Skip to content

Commit

Permalink
Prevent to reserve seats or signup is enrolment is not open
Browse files Browse the repository at this point in the history
- Enrolment is closed if enrolment_start_time is defined and is in the future or enrolment_end_time is defined and is in the past
- Show different warning message if enrolment is open but there is no available seats
- Add reserved seats to available seats amount if there is already reservation in the session storage
- Disable enrolment form also if there is no available places and seats reservation is not stored to session storage
  • Loading branch information
jorilindell committed Jun 14, 2023
1 parent 2c5ebab commit adeed9b
Show file tree
Hide file tree
Showing 12 changed files with 340 additions and 124 deletions.
1 change: 1 addition & 0 deletions public/locales/en/enrolment.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"titleNotifications": "Event notifications",
"titleRegistration": "Registration",
"warnings": {
"allSeatsReserved": "All seats in the event are currently booked. Please try again later.",
"capacityInWaitingList": "Registration for this event is still possible, but there are only {{count}} seat left in the queue.",
"capacityInWaitingList_other": "Registration for this event is still possible, but there are only {{count}} seats left in the queue.",
"capacityInWaitingListNoLimit": "Registration for this event is still possible, but there are only seats left in the queue.",
Expand Down
1 change: 1 addition & 0 deletions public/locales/fi/enrolment.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"titleNotifications": "Tapahtumaa koskevat ilmoitukset",
"titleRegistration": "Ilmoittautuminen",
"warnings": {
"allSeatsReserved": "Tapahtuman kaikki paikat ovat tällä hetkellä varatut. Kokeile myöhemmin uudelleen.",
"capacityInWaitingList": "Ilmoittautuminen tähän tapahtumaan on vielä mahdollista, mutta jonopaikkoja on jäljellä vain {{count}} kpl.",
"capacityInWaitingList_other": "Ilmoittautuminen tähän tapahtumaan on vielä mahdollista, mutta jonopaikkoja on jäljellä vain {{count}} kpl.",
"capacityInWaitingListNoLimit": "Ilmoittautuminen tähän tapahtumaan on vielä mahdollista, mutta vain jonopaikkoja on jäljellä.",
Expand Down
1 change: 1 addition & 0 deletions public/locales/sv/enrolment.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"titleNotifications": "Evenemangmeddelanden",
"titleRegistration": "Registrering",
"warnings": {
"allSeatsReserved": "Alla platser i evenemanget är för närvarande bokade. Vänligen försök igen senare.",
"capacityInWaitingList": "Registrering för detta evenemang är fortfarande möjligt, men det är bara {{count}} plats kvar i kön.",
"capacityInWaitingList_other": "Registrering för detta evenemang är fortfarande möjligt, men det är bara {{count}} platser kvar i kön.",
"capacityInWaitingListNoLimit": "Registrering till detta evenemang är fortfarande möjlig, men det finns endast platser kvar i kön.",
Expand Down
16 changes: 12 additions & 4 deletions src/domain/enrolment/enrolmentForm/EnrolmentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { IconCross, SingleSelectProps } from 'hds-react';
import pick from 'lodash/pick';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import React from 'react';
import React, { useMemo } from 'react';
import { ValidationError } from 'yup';
import 'react-toastify/dist/ReactToastify.css';

Expand Down Expand Up @@ -92,11 +92,19 @@ const EnrolmentForm: React.FC<Props> = ({
useEnrolmentPageContext();

const notificationOptions = useNotificationOptions();
const formDisabled = !isRegistrationPossible(registration);
const locale = useLocale();
const router = useRouter();
const { query } = router;

const formDisabled = useMemo(() => {
const data = getSeatsReservationData(registration.id as string);
if (data && !isSeatsReservationExpired(data)) {
return false;
}

return !isRegistrationPossible(registration);
}, [registration]);

const { serverErrorItems, setServerErrorItems, showServerErrors } =
useEnrolmentServerErrorsContext();

Expand Down Expand Up @@ -198,7 +206,7 @@ const EnrolmentForm: React.FC<Props> = ({
<ServerErrorSummary errors={serverErrorItems} />
<RegistrationWarning registration={registration} />

{!readOnly && (
{isRegistrationPossible(registration) && !readOnly && (
<>
<Divider />
<ReservationTimer
Expand All @@ -207,7 +215,7 @@ const EnrolmentForm: React.FC<Props> = ({
reservationTimerCallbacksDisabled.current
}
disableCallbacks={disableReservationTimerCallbacks}
initReservationData={!enrolment}
initReservationData={true}
registration={registration}
setAttendees={setAttendees}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useTranslation } from 'next-i18next';
import { FC } from 'react';
import { FC, useMemo } from 'react';

import { Registration } from '../../../registration/types';
import {
getFreeAttendeeCapacity,
getFreeWaitingListCapacity,
isAttendeeCapacityUsed,
} from '../../../registration/utils';
import {
getSeatsReservationData,
isSeatsReservationExpired,
} from '../../../reserveSeats/utils';

type Props = {
registration: Registration;
Expand All @@ -18,17 +22,22 @@ const AvailableSeatsText: FC<Props> = ({ registration }) => {
const attendeeCapacityUsed = isAttendeeCapacityUsed(registration);
const freeWaitingListCapacity = getFreeWaitingListCapacity(registration);

const reservedSeats = useMemo(() => {
const data = getSeatsReservationData(registration.id as string);
return data && !isSeatsReservationExpired(data) ? data.seats : 0;
}, [registration.id]);
return (
<>
{typeof freeAttendeeCapacity === 'number' && !attendeeCapacityUsed && (
<p>
{t('freeAttendeeCapacity')} <strong>{freeAttendeeCapacity}</strong>
{t('freeAttendeeCapacity')}{' '}
<strong>{freeAttendeeCapacity + reservedSeats}</strong>
</p>
)}
{attendeeCapacityUsed && typeof freeWaitingListCapacity === 'number' && (
<p>
{t('freeWaitingListCapacity')}{' '}
<strong>{freeWaitingListCapacity}</strong>
<strong>{freeWaitingListCapacity + reservedSeats}</strong>
</p>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
import React from 'react';

import { fakeRegistration } from '../../../../../utils/mockDataUtils';
import { RESERVATION_NAMES } from '../../../../../constants';
import {
fakeRegistration,
getMockedSeatsReservationData,
} from '../../../../../utils/mockDataUtils';
import { configure, render, screen } from '../../../../../utils/testUtils';
import { TEST_REGISTRATION_ID } from '../../../../registration/constants';
import { Registration } from '../../../../registration/types';
import { SeatsReservation } from '../../../../reserveSeats/types';
import AvailableSeatsText from '../AvailableSeatsText';

configure({ defaultHidden: true });

const renderComponent = (registration: Registration) =>
render(<AvailableSeatsText registration={registration} />);

test('should show amount of free seats ', () => {
const setSessionStorageValues = (reservation: SeatsReservation) => {
jest.spyOn(sessionStorage, 'getItem').mockImplementation((key: string) => {
switch (key) {
case `${RESERVATION_NAMES.ENROLMENT_RESERVATION}-${TEST_REGISTRATION_ID}`:
return reservation ? JSON.stringify(reservation) : '';
default:
return '';
}
});
};

test('should show amount of free seats', () => {
renderComponent(
fakeRegistration({
maximum_attendee_capacity: 10,
Expand All @@ -23,7 +40,7 @@ test('should show amount of free seats ', () => {
screen.getByText('7');
});

test('should show amount of remaining seats ', () => {
test('should show amount of remaining seats', () => {
renderComponent(
fakeRegistration({
maximum_attendee_capacity: 10,
Expand All @@ -36,6 +53,22 @@ test('should show amount of remaining seats ', () => {
screen.getByText('0');
});

test('should show amount of remaining seats if there ia reservation stored to session storage', () => {
const reservation = getMockedSeatsReservationData(1000);
setSessionStorageValues(reservation);
renderComponent(
fakeRegistration({
id: TEST_REGISTRATION_ID,
maximum_attendee_capacity: 10,
current_attendee_count: 3,
remaining_attendee_capacity: 0,
})
);

screen.getByText('Saatavilla olevia paikkoja');
screen.getByText(reservation.seats);
});

test('should show amount of free waiting list seats', () => {
renderComponent(
fakeRegistration({
Expand Down Expand Up @@ -67,3 +100,22 @@ test('should show amount of remaining waiting list seats ', () => {
screen.getByText('Saatavilla olevia jonopaikkoja');
screen.getByText('0');
});

test('should show amount of remaining waiting list seats if there ia reservation stored to session storage', () => {
const reservation = getMockedSeatsReservationData(1000);
setSessionStorageValues(reservation);
renderComponent(
fakeRegistration({
id: TEST_REGISTRATION_ID,
maximum_attendee_capacity: 10,
current_attendee_count: 10,
current_waiting_list_count: 3,
remaining_attendee_capacity: 0,
remaining_waiting_list_capacity: 0,
waiting_list_capacity: 10,
})
);

screen.getByText('Saatavilla olevia jonopaikkoja');
screen.getByText(reservation.seats);
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useField } from 'formik';
import { useTranslation } from 'next-i18next';
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';

import Button from '../../../common/components/button/Button';
import NumberInput from '../../../common/components/numberInput/NumberInput';
Expand All @@ -9,7 +9,10 @@ import {
getAttendeeCapacityError,
getFreeAttendeeOrWaitingListCapacity,
} from '../../registration/utils';
import { getSeatsReservationData } from '../../reserveSeats/utils';
import {
getSeatsReservationData,
isSeatsReservationExpired,
} from '../../reserveSeats/utils';
import { ENROLMENT_FIELDS, ENROLMENT_MODALS } from '../constants';
import { useEnrolmentPageContext } from '../enrolmentPageContext/hooks/useEnrolmentPageContext';
import { useEnrolmentServerErrorsContext } from '../enrolmentServerErrorsContext/hooks/useEnrolmentServerErrorsContext';
Expand Down Expand Up @@ -55,7 +58,17 @@ const ParticipantAmountSelector: React.FC<Props> = ({
t
);

const maxSeatAmount = getFreeAttendeeOrWaitingListCapacity(registration);
const reservedSeats = useMemo(() => {
const data = getSeatsReservationData(registration.id as string);
return data && !isSeatsReservationExpired(data) ? data.seats : 0;
}, [registration.id]);

const maxSeatAmount = useMemo(() => {
const freeCapacity = getFreeAttendeeOrWaitingListCapacity(registration);
return freeCapacity !== undefined
? freeCapacity + reservedSeats
: /* istanbul ignore next */ undefined;
}, [registration, reservedSeats]);

const { saving, updateSeatsReservation } = useSeatsReservationActions({
attendees,
Expand Down
13 changes: 11 additions & 2 deletions src/domain/enrolment/registrationWarning/RegistrationWarning.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Notification } from 'hds-react';
import { useTranslation } from 'next-i18next';
import React from 'react';
import React, { useMemo } from 'react';

import { Registration } from '../../registration/types';
import { getRegistrationWarning } from '../../registration/utils';
import {
getSeatsReservationData,
isSeatsReservationExpired,
} from '../../reserveSeats/utils';
import styles from './registrationWarning.module.scss';

type Props = {
Expand All @@ -14,7 +18,12 @@ const RegistrationWarning: React.FC<Props> = ({ registration }) => {
const { t } = useTranslation(['enrolment']);
const registrationWarning = getRegistrationWarning(registration, t);

return registrationWarning ? (
const hasReservation = useMemo(() => {
const data = getSeatsReservationData(registration.id as string);
return Boolean(data && !isSeatsReservationExpired(data));
}, [registration.id]);

return registrationWarning && !hasReservation ? (
<Notification className={styles.warning}>
{registrationWarning}
</Notification>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,60 @@ import addDays from 'date-fns/addDays';
import subDays from 'date-fns/subDays';
import React from 'react';

import { fakeRegistration } from '../../../../utils/mockDataUtils';
import { RESERVATION_NAMES } from '../../../../constants';
import {
fakeRegistration,
getMockedSeatsReservationData,
} from '../../../../utils/mockDataUtils';
import { render, screen } from '../../../../utils/testUtils';
import { TEST_REGISTRATION_ID } from '../../../registration/constants';
import { Registration } from '../../../registration/types';
import { SeatsReservation } from '../../../reserveSeats/types';
import RegistrationWarning from '../RegistrationWarning';

const renderComponent = (registration: Registration) =>
render(<RegistrationWarning registration={registration} />);

test('should show warning if registration is full', async () => {
const now = new Date();
const enrolment_start_time = subDays(now, 1).toISOString();
const enrolment_end_time = addDays(now, 1).toISOString();
const registration = fakeRegistration({
current_attendee_count: 10,
current_waiting_list_count: 5,
enrolment_end_time,
enrolment_start_time,
maximum_attendee_capacity: 10,
waiting_list_capacity: 5,
const now = new Date();
const enrolment_start_time = subDays(now, 1).toISOString();
const enrolment_end_time = addDays(now, 1).toISOString();
const registration = fakeRegistration({
current_attendee_count: 10,
current_waiting_list_count: 5,
enrolment_end_time,
enrolment_start_time,
id: TEST_REGISTRATION_ID,
maximum_attendee_capacity: 10,
waiting_list_capacity: 5,
});

const setSessionStorageValues = (reservation: SeatsReservation) => {
jest.spyOn(sessionStorage, 'getItem').mockImplementation((key: string) => {
switch (key) {
case `${RESERVATION_NAMES.ENROLMENT_RESERVATION}-${TEST_REGISTRATION_ID}`:
return reservation ? JSON.stringify(reservation) : '';
default:
return '';
}
});
};

test('should show warning if registration is full', async () => {
renderComponent(registration);

screen.getByText(
'Ilmoittautuminen tähän tapahtumaan on tällä hetkellä suljettu. Kokeile myöhemmin uudelleen.'
await screen.getByText(
'Tapahtuman kaikki paikat ovat tällä hetkellä varatut. Kokeile myöhemmin uudelleen.'
);
});

test('should not show warning if registration is full but user has reservation', async () => {
const reservation = getMockedSeatsReservationData(1000);
setSessionStorageValues(reservation);
renderComponent(registration);

expect(
screen.queryByText(
'Tapahtuman kaikki paikat ovat tällä hetkellä varatut. Kokeile myöhemmin uudelleen.'
)
).not.toBeInTheDocument();
});
Loading

0 comments on commit adeed9b

Please sign in to comment.