diff --git a/apps/ui/components/LoginFragment.tsx b/apps/ui/components/LoginFragment.tsx
index ebd3b263b..797d6902f 100644
--- a/apps/ui/components/LoginFragment.tsx
+++ b/apps/ui/components/LoginFragment.tsx
@@ -2,53 +2,40 @@ import React from "react";
import { useTranslation } from "next-i18next";
import { signIn } from "common/src/browserHelpers";
import { useSession } from "@/hooks/auth";
-import { SubmitButton } from "@/styles/util";
+import { Button } from "hds-react";
type Props = {
apiBaseUrl: string;
- text?: string;
- componentIfAuthenticated?: React.ReactNode;
+ componentIfAuthenticated: JSX.Element;
isActionDisabled?: boolean;
- actionCallback?: () => void;
returnUrl?: string;
};
-function LoginFragment({
+export function LoginFragment({
apiBaseUrl,
- text,
componentIfAuthenticated,
isActionDisabled,
- actionCallback,
returnUrl,
-}: Props): JSX.Element | null {
+}: Props): JSX.Element {
// TODO pass the isAuthenticated from SSR and remove the hook
const { isAuthenticated } = useSession();
const { t } = useTranslation();
- return !isAuthenticated ? (
-
- {
- if (actionCallback) {
- actionCallback();
- }
- if (returnUrl) {
- signIn(apiBaseUrl, returnUrl);
- return;
- }
- signIn(apiBaseUrl);
- }}
- aria-label={t("reservationCalendar:loginAndReserve")}
- className="login-fragment__button--login"
- disabled={isActionDisabled}
- >
- {t("reservationCalendar:loginAndReserve")}
-
- {text}
-
- ) : (
- {componentIfAuthenticated}
+ const handleClick = () => {
+ signIn(apiBaseUrl, returnUrl);
+ };
+
+ if (isAuthenticated) {
+ return componentIfAuthenticated;
+ }
+
+ return (
+
);
}
-
-export default LoginFragment;
diff --git a/apps/ui/components/recurring/StartApplicationBar.tsx b/apps/ui/components/recurring/StartApplicationBar.tsx
index 31381953a..e591a4e6f 100644
--- a/apps/ui/components/recurring/StartApplicationBar.tsx
+++ b/apps/ui/components/recurring/StartApplicationBar.tsx
@@ -22,6 +22,8 @@ import { useDisplayError } from "@/hooks/useDisplayError";
import { useReservationUnitList } from "@/hooks";
import { useSearchParams } from "next/navigation";
import { pageSideMargins } from "common/styles/layout";
+import { LoginFragment } from "../LoginFragment";
+import { getPostLoginUrl } from "@/modules/util";
const BackgroundContainer = styled.div`
position: fixed;
@@ -60,9 +62,11 @@ type Props = {
applicationRound: {
reservationUnits: NodeList;
};
+ apiBaseUrl: string;
};
export function StartApplicationBar({
+ apiBaseUrl,
applicationRound,
}: Props): JSX.Element | null {
const { t } = useTranslation();
@@ -138,17 +142,25 @@ export function StartApplicationBar({
? t("shoppingCart:deleteSelectionsShort")
: t("shoppingCart:deleteSelections")}
- }
- colorVariant="light"
- >
- {t("shoppingCart:nextShort")}
-
+ storeReservationForLogin()}
+ apiBaseUrl={apiBaseUrl}
+ componentIfAuthenticated={
+ }
+ colorVariant="light"
+ >
+ {t("shoppingCart:nextShort")}
+
+ }
+ />
);
diff --git a/apps/ui/hooks/useRemoveStoredReservation.ts b/apps/ui/hooks/useRemoveStoredReservation.ts
new file mode 100644
index 000000000..793ed6bf9
--- /dev/null
+++ b/apps/ui/hooks/useRemoveStoredReservation.ts
@@ -0,0 +1,14 @@
+import { ReservationProps } from "@/context/DataContext";
+import { useEffect } from "react";
+import { useLocalStorage } from "react-use";
+
+/// Local storage usage has been removed
+/// leaving this hook for now to cleanup any existing users
+export function useRemoveStoredReservation() {
+ const [storedReservation, , removeStoredReservation] =
+ useLocalStorage("reservation");
+
+ useEffect(() => {
+ if (storedReservation) removeStoredReservation();
+ }, [storedReservation, removeStoredReservation]);
+}
diff --git a/apps/ui/modules/util.ts b/apps/ui/modules/util.ts
index bfdac5bc5..e1f60dc69 100644
--- a/apps/ui/modules/util.ts
+++ b/apps/ui/modules/util.ts
@@ -14,6 +14,7 @@ import {
timeToMinutes,
type LocalizationLanguages,
} from "common/src/helpers";
+import { ReadonlyURLSearchParams } from "next/navigation";
export { formatDuration } from "common/src/common/util";
export { fromAPIDate, fromUIDate };
@@ -120,14 +121,19 @@ export const getAddressAlt = (ru: {
export const isTouchDevice = (): boolean =>
isBrowser && window?.matchMedia("(any-hover: none)").matches;
-export function getPostLoginUrl() {
+export function getPostLoginUrl(
+ params: Readonly = new ReadonlyURLSearchParams()
+): string | undefined {
if (!isBrowser) {
return undefined;
}
const { origin, pathname, searchParams } = new URL(window.location.href);
- const params = new URLSearchParams(searchParams);
- params.set("isPostLogin", "true");
- return `${origin}${pathname}?${params.toString()}`;
+ const p = new URLSearchParams(searchParams);
+ for (const [key, value] of params) {
+ p.append(key, value);
+ }
+ p.set("isPostLogin", "true");
+ return `${origin}${pathname}?${p.toString()}`;
}
// date format should always be in finnish, but the weekday and time separator should be localized
diff --git a/apps/ui/pages/recurring/[id]/index.tsx b/apps/ui/pages/recurring/[id]/index.tsx
index 915101f0e..2cc70297e 100644
--- a/apps/ui/pages/recurring/[id]/index.tsx
+++ b/apps/ui/pages/recurring/[id]/index.tsx
@@ -9,6 +9,12 @@ import {
ApplicationRoundDocument,
type ApplicationRoundQuery,
type ApplicationRoundQueryVariables,
+ type CreateApplicationMutationVariables,
+ type CreateApplicationMutation,
+ CreateApplicationDocument,
+ type ApplicationCreateMutationInput,
+ CurrentUserDocument,
+ type CurrentUserQuery,
} from "@gql/gql-types";
import {
base64encode,
@@ -28,14 +34,17 @@ import { useSearchQuery } from "@/hooks/useSearchQuery";
import { SortingComponent } from "@/components/SortingComponent";
import { useSearchParams } from "next/navigation";
import { Breadcrumb } from "@/components/common/Breadcrumb";
-import { seasonalPrefix } from "@/modules/urls";
+import { getApplicationPath, seasonalPrefix } from "@/modules/urls";
import { getApplicationRoundName } from "@/modules/applicationRound";
import { gql } from "@apollo/client";
function SeasonalSearch({
+ apiBaseUrl,
applicationRound,
options,
-}: Readonly>): JSX.Element {
+}: Readonly<
+ Pick
+>): JSX.Element {
const { t, i18n } = useTranslation();
const searchValues = useSearchParams();
@@ -102,7 +111,10 @@ function SeasonalSearch({
fetchMore={(cursor) => fetchMore(cursor)}
sortingComponent={}
/>
-
+
>
);
}
@@ -111,10 +123,11 @@ type Props = Awaited>["props"];
type NarrowedProps = Exclude;
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
- const { locale, params } = ctx;
+ const { locale, params, query } = ctx;
const commonProps = getCommonServerSideProps();
const apolloClient = createApolloClient(commonProps.apiBaseUrl, ctx);
const pk = toNumber(ignoreMaybeArray(params?.id));
+ const isPostLogin = query.isPostLogin === "true";
const notFound = {
notFound: true,
@@ -141,7 +154,49 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
return notFound;
}
- const opts = await getSearchOptions(apolloClient, "seasonal", locale ?? "");
+ const { data: userData } = await apolloClient.query({
+ query: CurrentUserDocument,
+ });
+
+ if (isPostLogin && userData.currentUser != null) {
+ const input: ApplicationCreateMutationInput = {
+ applicationRound: applicationRound.pk ?? 0,
+ };
+
+ // don't catch errors here -> results in 500 page
+ // we can't display proper error message to user (no page for it)
+ // and we can't handle them
+ const mutRes = await apolloClient.mutate<
+ CreateApplicationMutation,
+ CreateApplicationMutationVariables
+ >({
+ mutation: CreateApplicationDocument,
+ variables: {
+ input,
+ },
+ });
+
+ if (mutRes.data?.createApplication?.pk) {
+ const { pk } = mutRes.data.createApplication;
+ const selected = query.selectedReservationUnits ?? [];
+ const forwardParams = new URLSearchParams();
+ for (const s of selected) {
+ forwardParams.append("selectedReservationUnits", s);
+ }
+ const url = `${getApplicationPath(pk, "page1")}?${forwardParams.toString()}`;
+ return {
+ redirect: {
+ permanent: false,
+ destination: url,
+ },
+ props: {
+ notFound: true, // for prop narrowing
+ },
+ };
+ }
+ }
+
+ const opts = await getSearchOptions(apolloClient, "seasonal", locale ?? "fi");
return {
props: {
...commonProps,
diff --git a/apps/ui/pages/reservation-unit/[...params].tsx b/apps/ui/pages/reservation-unit/[...params].tsx
index 536f832a1..3b0115846 100644
--- a/apps/ui/pages/reservation-unit/[...params].tsx
+++ b/apps/ui/pages/reservation-unit/[...params].tsx
@@ -1,8 +1,7 @@
-import React, { useCallback, useEffect, useMemo, useState } from "react";
+import React, { useCallback, useMemo, useState } from "react";
import styled from "styled-components";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useRouter } from "next/router";
-import { useLocalStorage } from "react-use";
import { Stepper } from "hds-react";
import { FormProvider, useForm } from "react-hook-form";
import type { GetServerSidePropsContext } from "next";
@@ -21,6 +20,7 @@ import {
} from "@gql/gql-types";
import { type Inputs } from "common/src/reservation-form/types";
import { createApolloClient } from "@/modules/apolloClient";
+import { default as NextError } from "next/error";
import {
getReservationPath,
getReservationUnitPath,
@@ -29,7 +29,6 @@ import {
import { Sanitize } from "common/src/components/Sanitize";
import { isReservationUnitFreeOfCharge } from "@/modules/reservationUnit";
import { getCheckoutUrl } from "@/modules/reservation";
-import { ReservationProps } from "@/context/DataContext";
import { ReservationInfoCard } from "@/components/reservation/ReservationInfoCard";
import { Step0 } from "@/components/reservation/Step0";
import { Step1 } from "@/components/reservation/Step1";
@@ -51,6 +50,7 @@ import { Flex } from "common/styles/util";
import { Breadcrumb } from "@/components/common/Breadcrumb";
import { ReservationPageWrapper } from "@/components/reservations/styles";
import { useDisplayError } from "@/hooks/useDisplayError";
+import { useRemoveStoredReservation } from "@/hooks/useRemoveStoredReservation";
const StyledReservationInfoCard = styled(ReservationInfoCard)`
grid-column: 1 / -1;
@@ -86,18 +86,6 @@ const TitleSection = styled(Flex)`
}
`;
-/// We want to get rid of the local storage
-/// but there is still code that requires it to be used.
-/// Other pages (ex. login + book) get confused if we don't clear it here.
-const useRemoveStoredReservation = () => {
- const [storedReservation, , removeStoredReservation] =
- useLocalStorage("reservation");
-
- useEffect(() => {
- if (storedReservation) removeStoredReservation();
- }, [storedReservation, removeStoredReservation]);
-};
-
// NOTE back / forward buttons (browser) do NOT work properly
// router.beforePopState could be used to handle them but it's super hackish
// the correct solution is to create separate pages (files) for each step (then next-router does this for free)
@@ -122,15 +110,6 @@ function NewReservation(props: PropsNarrowed): JSX.Element | null {
const reservation = resData?.reservation ?? props.reservation;
const reservationUnit = reservation?.reservationUnits?.find(() => true);
- // it should be Created only here (SSR should have redirected)
- if (reservation.state !== ReservationStateChoice.Created) {
- // eslint-disable-next-line no-console
- console.warn(
- "should NOT be here when reservation state is ",
- reservation.state
- );
- }
-
useRemoveStoredReservation();
const [step, setStep] = useState(0);
@@ -352,6 +331,11 @@ function NewReservation(props: PropsNarrowed): JSX.Element | null {
}
};
+ // it should be Created only here (SSR should have redirected)
+ if (reservation.state !== ReservationStateChoice.Created) {
+ return ;
+ }
+
return (
diff --git a/apps/ui/pages/reservation-unit/[id].tsx b/apps/ui/pages/reservation-unit/[id].tsx
index 25b1b046a..c5a1ecf88 100644
--- a/apps/ui/pages/reservation-unit/[id].tsx
+++ b/apps/ui/pages/reservation-unit/[id].tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useMemo, useState } from "react";
+import React, { useMemo, useState } from "react";
import type { GetServerSidePropsContext } from "next";
import { Trans, useTranslation } from "next-i18next";
import { useRouter } from "next/router";
@@ -14,9 +14,7 @@ import {
toUIDate,
} from "common/src/common/util";
import { formatters as getFormatters, H4 } from "common";
-import { useLocalStorage } from "react-use";
import { breakpoints } from "common/src/common/style";
-import { type PendingReservation } from "@/modules/types";
import {
type ApplicationRoundTimeSlotNode,
type ReservationCreateMutationInput,
@@ -27,12 +25,21 @@ import {
RelatedReservationUnitsDocument,
type RelatedReservationUnitsQuery,
type RelatedReservationUnitsQueryVariables,
+ CreateReservationDocument,
+ type CreateReservationMutation,
+ type CreateReservationMutationVariables,
+ CurrentUserDocument,
+ type CurrentUserQuery,
+ type TimeSlotType,
} from "@gql/gql-types";
import {
base64encode,
filterNonNullable,
+ formatTimeRange,
fromMondayFirstUnsafe,
+ ignoreMaybeArray,
isPriceFree,
+ timeToMinutes,
toNumber,
} from "common/src/helpers";
import { Head } from "@/components/reservation-unit/Head";
@@ -84,7 +91,7 @@ import {
PendingReservationFormSchema,
type PendingReservationFormType,
} from "@/components/reservation-unit/schema";
-import LoginFragment from "@/components/LoginFragment";
+import { LoginFragment } from "@/components/LoginFragment";
import { RELATED_RESERVATION_STATES } from "common/src/const";
import { useReservableTimes } from "@/hooks/useReservableTimes";
import { ReservationTimePicker } from "@/components/reservation/ReservationTimePicker";
@@ -98,18 +105,66 @@ import { Breadcrumb } from "@/components/common/Breadcrumb";
import { Flex } from "common/styles/util";
import { SubmitButton } from "@/styles/util";
import { useDisplayError } from "@/hooks/useDisplayError";
+import { useRemoveStoredReservation } from "@/hooks/useRemoveStoredReservation";
type Props = Awaited>["props"];
type PropsNarrowed = Exclude;
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const { params, query, locale } = ctx;
- const pk = Number(params?.id);
+ const pk = toNumber(ignoreMaybeArray(params?.id));
const uuid = query.ru;
const commonProps = getCommonServerSideProps();
const apolloClient = createApolloClient(commonProps.apiBaseUrl, ctx);
- if (pk) {
+ const notFound = {
+ props: {
+ ...commonProps,
+ ...(await serverSideTranslations(locale ?? "fi")),
+ notFound: true, // required for type narrowing
+ },
+ notFound: true,
+ };
+
+ const isPostLogin = query.isPostLogin === "true";
+
+ // recheck login status in case user cancelled the login
+ const { data: userData } = await apolloClient.query({
+ query: CurrentUserDocument,
+ });
+ if (pk != null && pk > 0 && isPostLogin && userData?.currentUser != null) {
+ const begin = ignoreMaybeArray(query.begin);
+ const end = ignoreMaybeArray(query.end);
+
+ if (begin != null && end != null) {
+ const input: ReservationCreateMutationInput = {
+ begin,
+ end,
+ reservationUnit: pk,
+ };
+ const res = await apolloClient.mutate<
+ CreateReservationMutation,
+ CreateReservationMutationVariables
+ >({
+ mutation: CreateReservationDocument,
+ variables: {
+ input,
+ },
+ });
+ const { pk: reservationPk } = res.data?.createReservation ?? {};
+ return {
+ redirect: {
+ destination: getReservationInProgressPath(pk, reservationPk),
+ permanent: false,
+ },
+ props: {
+ notFound: true, // required for type narrowing
+ },
+ };
+ }
+ }
+
+ if (pk != null && pk > 0) {
const today = new Date();
const startDate = today;
const endDate = addYears(today, 2);
@@ -121,7 +176,6 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
ReservationUnitPageQueryVariables
>({
query: ReservationUnitPageDocument,
- fetchPolicy: "no-cache",
variables: {
id,
beginDate: toApiDate(startDate) ?? "",
@@ -138,24 +192,12 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const reservationUnit = reservationUnitData?.reservationUnit ?? undefined;
if (!isReservationUnitPublished(reservationUnit) && !previewPass) {
- return {
- props: {
- ...commonProps,
- notFound: true, // required for type narrowing
- },
- notFound: true,
- };
+ return notFound;
}
const isDraft = reservationUnit?.isDraft;
if (isDraft && !previewPass) {
- return {
- props: {
- ...commonProps,
- notFound: true, // required for type narrowing
- },
- notFound: true,
- };
+ return notFound;
}
const bookingTerms = await getGenericTerms(apolloClient);
@@ -179,13 +221,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
}
if (!reservationUnit?.pk) {
- return {
- props: {
- ...commonProps,
- notFound: true, // required for type narrowing
- },
- notFound: true,
- };
+ return notFound;
}
const reservableTimeSpans = filterNonNullable(
@@ -194,9 +230,9 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const queryParams = new URLSearchParams(query as Record);
const searchDate = queryParams.get("date") ?? null;
const searchTime = queryParams.get("time") ?? null;
- const searchDuration = Number.isNaN(Number(queryParams.get("duration")))
- ? null
- : Number(queryParams.get("duration"));
+ const searchDuration = toNumber(
+ ignoreMaybeArray(queryParams.get("duration"))
+ );
const blockingReservations = filterNonNullable(
reservationUnitData?.affectingReservations
@@ -212,7 +248,6 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
relatedReservationUnits,
activeApplicationRounds,
termsOfUse: { genericTerms: bookingTerms },
- isPostLogin: query?.isPostLogin === "true",
searchDuration,
searchDate,
searchTime,
@@ -220,15 +255,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
};
}
- return {
- props: {
- ...commonProps,
- ...(await serverSideTranslations(locale ?? "fi")),
- notFound: true, // required for type narrowing
- paramsId: pk,
- },
- notFound: true,
- };
+ return notFound;
}
const StyledApplicationRoundScheduleDay = styled.p`
@@ -240,19 +267,26 @@ const StyledApplicationRoundScheduleDay = styled.p`
}
`;
+function formatTimeSlot(slot: TimeSlotType): string {
+ const { begin, end } = slot;
+ if (!begin || !end) {
+ return "";
+ }
+ const beginTime = timeToMinutes(begin);
+ const endTime = timeToMinutes(end);
+ const endTimeChecked = endTime === 0 ? 24 * 60 : endTime;
+ return formatTimeRange(beginTime, endTimeChecked, true);
+}
+
// Returns an element for a weekday in the application round timetable, with up to two timespans
function ApplicationRoundScheduleDay(
- props: Omit
+ props: Pick<
+ ApplicationRoundTimeSlotNode,
+ "weekday" | "reservableTimes" | "closed"
+ >
) {
const { t } = useTranslation();
const { weekday, reservableTimes, closed } = props;
- const noSeconds = (time: string) => time.split(":").slice(0, 2).join(":");
- const timeSlotString = (idx: number): string =>
- reservableTimes?.[idx]?.begin && reservableTimes?.[idx]?.end
- ? `${noSeconds(String(reservableTimes?.[idx]?.begin))}-${noSeconds(
- String(reservableTimes?.[idx]?.end)
- )}`
- : "";
return (
@@ -263,8 +297,9 @@ function ApplicationRoundScheduleDay(
) : (
reservableTimes && (
- {reservableTimes[0] && timeSlotString(0)}
- {reservableTimes[1] && ` ${t("common:and")} ${timeSlotString(1)}`}
+ {reservableTimes[0] && formatTimeSlot(reservableTimes[0])}
+ {reservableTimes[1] &&
+ ` ${t("common:and")} ${formatTimeSlot(reservableTimes[1])}`}
)
)}
@@ -276,19 +311,30 @@ function SubmitFragment(
props: Readonly<{
focusSlot: FocusTimeSlot;
apiBaseUrl: string;
- actionCallback: () => void;
reservationForm: UseFormReturn;
loadingText: string;
buttonText: string;
}>
-) {
+): JSX.Element {
const { isSubmitting } = props.reservationForm.formState;
+ const { focusSlot } = props;
const { isReservable } = props.focusSlot;
+ const returnToUrl = useMemo(() => {
+ if (!focusSlot.isReservable) {
+ return;
+ }
+ const { start: begin, end } = focusSlot ?? {};
+
+ const params = new URLSearchParams();
+ params.set("begin", begin.toISOString());
+ params.set("end", end.toISOString());
+ return getPostLoginUrl(params);
+ }, [focusSlot]);
+
return (
}
- returnUrl={getPostLoginUrl()}
+ returnUrl={returnToUrl}
/>
);
}
@@ -328,7 +374,6 @@ function ReservationUnit({
activeApplicationRounds,
blockingReservations,
termsOfUse,
- isPostLogin,
apiBaseUrl,
searchDuration,
searchDate,
@@ -336,6 +381,7 @@ function ReservationUnit({
}: PropsNarrowed): JSX.Element | null {
const { t } = useTranslation();
const router = useRouter();
+ useRemoveStoredReservation();
const [isDialogOpen, setIsDialogOpen] = useState(false);
@@ -455,7 +501,7 @@ function ReservationUnit({
return reservationUnit.canApplyFreeOfCharge && isPaid;
}, [reservationUnit.canApplyFreeOfCharge, reservationUnit.pricings]);
- const [addReservation] = useCreateReservationMutation();
+ const [createReservationMutation] = useCreateReservationMutation();
const displayError = useDisplayError();
@@ -466,7 +512,7 @@ function ReservationUnit({
if (reservationUnit.pk == null) {
throw new Error("Reservation unit pk is missing");
}
- const res = await addReservation({
+ const res = await createReservationMutation({
variables: {
input,
},
@@ -475,9 +521,7 @@ function ReservationUnit({
if (pk == null) {
throw new Error("Reservation creation failed");
}
- if (reservationUnit.pk != null) {
- router.push(getReservationInProgressPath(reservationUnit.pk, pk));
- }
+ router.push(getReservationInProgressPath(reservationUnit.pk, pk));
} catch (err) {
displayError(err);
}
@@ -491,70 +535,8 @@ function ReservationUnit({
console.warn("not reservable because: ", reason);
}
- const [storedReservation, setStoredReservation] =
- useLocalStorage("reservation");
-
- const storeReservationForLogin = useCallback(() => {
- if (reservationUnit.pk == null) {
- return;
- }
- if (!focusSlot.isReservable) {
- return;
- }
+ const shouldDisplayBottomWrapper = relatedReservationUnits?.length > 0;
- const { start, end } = focusSlot ?? {};
- // NOTE the only place where we use ISO strings since they are always converted to Date objects
- // another option would be to refactor storaged reservation to use Date objects
- setStoredReservation({
- begin: start.toISOString(),
- end: end.toISOString(),
- price: undefined,
- reservationUnitPk: reservationUnit.pk ?? 0,
- });
- }, [focusSlot, reservationUnit.pk, setStoredReservation]);
-
- // If returning from login, continue on to reservation details
- useEffect(() => {
- if (
- !!isPostLogin &&
- storedReservation &&
- !isReservationQuotaReached &&
- reservationUnit?.pk
- ) {
- const { begin, end } = storedReservation;
- const input: ReservationCreateMutationInput = {
- begin,
- end,
- reservationUnit: reservationUnit.pk,
- };
- createReservation(input);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- /* TODO why is this needed? do we need to reset the form values?
- useEffect(() => {
- const { begin, end } = storedReservation ?? {};
- if (begin == null || end == null) {
- return;
- }
-
- const beginDate = new Date(begin);
- const endDate = new Date(end);
-
- // TODO why? can't we set it using the form or can we make an intermediate reset function
- handleCalendarEventChange({
- start: beginDate,
- end: endDate,
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [storedReservation?.begin, storedReservation?.end]);
- */
-
- const shouldDisplayBottomWrapper = useMemo(
- () => relatedReservationUnits?.length > 0,
- [relatedReservationUnits?.length]
- );
const termsOfUseContent = getTranslation(reservationUnit, "termsOfUse");
const paymentTermsContent = reservationUnit.paymentTerms
? getTranslation(reservationUnit.paymentTerms, "text")
@@ -576,13 +558,12 @@ function ReservationUnit({
storeReservationForLogin()}
reservationForm={reservationForm}
loadingText={t("reservationCalendar:makeReservationLoading")}
buttonText={t("reservationCalendar:makeReservation")}
/>
),
- [apiBaseUrl, focusSlot, reservationForm, storeReservationForLogin, t]
+ [apiBaseUrl, focusSlot, reservationForm, t]
);
const startingTimeOptions = getPossibleTimesForDay({
diff --git a/packages/common/src/browserHelpers.ts b/packages/common/src/browserHelpers.ts
index 23048c1d3..70ddf17e5 100644
--- a/packages/common/src/browserHelpers.ts
+++ b/packages/common/src/browserHelpers.ts
@@ -21,7 +21,8 @@ export function signIn(apiBaseUrl: string, returnUrl?: unknown) {
throw new Error("signIn can only be called in the browser");
}
const returnUrlParam = cleanupUrlParam(returnUrl);
- const returnTo = returnUrlParam ?? window.location.href;
+ // encode otherwise backend will drop the query params
+ const returnTo = encodeURIComponent(returnUrlParam ?? window.location.href);
const url = getSignInUrl(apiBaseUrl, returnTo);
window.location.href = url;
}
diff --git a/packages/common/src/helpers.ts b/packages/common/src/helpers.ts
index d8cd2de7a..cc81fe9b7 100644
--- a/packages/common/src/helpers.ts
+++ b/packages/common/src/helpers.ts
@@ -211,6 +211,14 @@ export function formatMinutes(
return `${hours}`;
}
+export function formatTimeRange(
+ begin: number,
+ end: number,
+ trailingMinutes = false
+): string {
+ return `${formatMinutes(begin, trailingMinutes)}–${formatMinutes(end, trailingMinutes)}`;
+}
+
export function formatApiTimeInterval({
beginTime,
endTime,