;
@@ -80,7 +80,8 @@ function ReservationNotification({
disabled?: boolean;
isLoading?: boolean;
}) {
- const startRemainingMinutes = reservation.paymentOrder[0]?.expiresInMinutes;
+ const order = reservation.paymentOrder.find(() => true);
+ const startRemainingMinutes = order?.expiresInMinutes;
const [remainingMinutes, setRemainingMinutes] = useState(
startRemainingMinutes
);
@@ -104,6 +105,7 @@ function ReservationNotification({
}
return minutes - 1;
}
+
useEffect(() => {
const paymentTimeout = setTimeout(() => {
const minutes = remainingMinutes ?? 0;
@@ -113,9 +115,11 @@ function ReservationNotification({
return clearTimeout(paymentTimeout);
}
}, [remainingMinutes, isCreated]);
+
if (!isCreated && !remainingMinutes) {
return null;
}
+
return (
{text}
-
{t("notification:waitingForPayment.cancelReservation")}
-
-
+
+
@@ -201,16 +205,13 @@ export function InProgressReservationNotification() {
.filter(() => !shouldHideCreatedNotification)
.find((r) => r.state === ReservationStateChoice.Created);
- const {
- mutation: deleteReservation,
- deleted,
- error: deleteError,
- isLoading: isDeleteLoading,
- } = useDeleteReservation();
+ const [
+ deleteReservation,
+ { data: deleteData, error: deleteError, loading: isDeleteLoading },
+ ] = useDeleteReservationMutation();
+ const deleted = deleteData?.deleteReservation?.deleted;
- const { order } = useOrder({
- orderUuid: unpaidReservation?.paymentOrder[0]?.orderUuid ?? undefined,
- });
+ const order = unpaidReservation?.paymentOrder[0];
const checkoutUrl = getCheckoutUrl(order, i18n.language);
useEffect(() => {
diff --git a/apps/ui/components/reservations/styles.tsx b/apps/ui/components/reservations/styles.tsx
index 33274831e6..a903c64ad0 100644
--- a/apps/ui/components/reservations/styles.tsx
+++ b/apps/ui/components/reservations/styles.tsx
@@ -1,24 +1,38 @@
-import { Container } from "common";
import styled from "styled-components";
import { breakpoints } from "common/src/common/style";
-import { H2 } from "common/src/common/typography";
+import { H1 } from "common/src/common/typography";
/* TODO margins should be in page layout component, not custom for every page */
-export const ReservationPageWrapper = styled(Container)`
+// TODO needs to have some margin / padding at the top? because we have a second column on dekstop
+// and that should not be clued to the top (so we can't use H margins)
+// TODO for edit page should have more rows
+// (because the calendar should be taking more space without affecting the other elements)
+export const ReservationPageWrapper = styled.div<{ $nRows?: number }>`
display: grid;
grid-template-columns: 1fr;
- grid-template-rows: repeat(4, auto);
+ grid-template-rows: ${({ $nRows }) => `repeat(${$nRows ?? 4}, auto)`};
grid-gap: var(--spacing-m);
justify-content: space-between;
- margin-top: var(--spacing-l);
+ margin: 0;
+ padding: 0;
+ @media (width > ${breakpoints.m}) {
+ grid-template-columns: 2fr 1fr;
+ }
+`;
+
+// Larger breakpoint for reservation unit page because Calendar takes more space.
+export const ReservationUnitPageWrapper = styled(ReservationPageWrapper)`
+ @media (width > ${breakpoints.m}) {
+ grid-template-columns: 1fr;
+ }
@media (width > ${breakpoints.l}) {
grid-template-columns: 2fr 1fr;
}
`;
/* There is no breadcrumbs on this page so remove the margin */
-export const Heading = styled(H2).attrs({ as: "h1" })`
+// TODO edit page at least should have this full sized
+export const Heading = styled(H1)`
grid-column: 1 / -1;
- margin-top: 0;
`;
diff --git a/apps/ui/components/search/FilterTagList.tsx b/apps/ui/components/search/FilterTagList.tsx
index 7613a38cc3..b723de4a81 100644
--- a/apps/ui/components/search/FilterTagList.tsx
+++ b/apps/ui/components/search/FilterTagList.tsx
@@ -5,9 +5,9 @@ import { useSearchModify, useSearchValues } from "@/hooks/useSearchValues";
import { type TFunction } from "i18next";
type FilterTagProps = {
- filters: string[];
- multiSelectFilters: string[];
- hideList: string[];
+ filters: readonly string[];
+ multiSelectFilters: readonly string[];
+ hideList: readonly string[];
translateTag: (key: string, value: string) => string | undefined;
};
diff --git a/apps/ui/components/search/SeasonalSearchForm.tsx b/apps/ui/components/search/SeasonalSearchForm.tsx
index d876d03e42..6a2110e564 100644
--- a/apps/ui/components/search/SeasonalSearchForm.tsx
+++ b/apps/ui/components/search/SeasonalSearchForm.tsx
@@ -1,133 +1,20 @@
-import React, { ReactNode, useState } from "react";
+import React from "react";
import { useTranslation } from "next-i18next";
-import {
- TextInput,
- IconSearch,
- Button,
- IconAngleUp,
- IconAngleDown,
-} from "hds-react";
+import { TextInput, IconSearch } from "hds-react";
import { type SubmitHandler, useForm } from "react-hook-form";
-import styled from "styled-components";
-import { breakpoints } from "common/src/common/style";
import { participantCountOptions } from "@/modules/const";
-import { MediumButton } from "@/styles/util";
-import { JustForDesktop, JustForMobile } from "@/modules/style/layout";
import { useSearchModify, useSearchValues } from "@/hooks/useSearchValues";
import { FilterTagList } from "./FilterTagList";
import { ParsedUrlQuery } from "node:querystring";
import { ControlledSelect } from "common/src/components/form/ControlledSelect";
import { ControlledMultiSelect } from "./ControlledMultiSelect";
+import { BottomContainer, Filters, StyledSubmitButton } from "./styled";
import {
mapQueryParamToNumber,
mapQueryParamToNumberArray,
mapSingleParamToFormValue,
} from "@/modules/search";
-
-const desktopBreakpoint = "840px";
-
-const TopContainer = styled.div`
- display: grid;
- grid-template-columns: 1fr;
- gap: var(--spacing-m);
- align-items: flex-end;
-
- @media (min-width: ${desktopBreakpoint}) {
- grid-template-columns: 1fr 154px;
- }
-`;
-
-const FilterToggleWrapper = styled.div`
- display: grid;
- justify-items: center;
- margin: var(--spacing-xs) 0;
-`;
-
-const Hr = styled.hr`
- border-color: var(--color-black-60);
- border-style: solid;
-`;
-
-const Filters = styled.div<{ $areFiltersVisible: boolean }>`
- margin-top: 0;
- max-width: 100%;
- display: grid;
- grid-template-columns: 1fr;
- grid-gap: var(--spacing-m);
- font-size: var(--fontsize-body-m);
-
- ${({ $areFiltersVisible }) =>
- !$areFiltersVisible &&
- `
- @media (max-width: ${desktopBreakpoint}) {
- & > *:nth-child(n + 3) {
- display: none;
- }}
- `}
-
- label {
- font-family: var(--font-medium);
- font-weight: 500;
- }
-
- @media (min-width: ${breakpoints.m}) {
- margin-top: var(--spacing-s);
- grid-template-columns: 1fr 1fr;
- }
-
- @media (min-width: ${breakpoints.l}) {
- grid-template-columns: 1fr 1fr 1fr;
- }
-`;
-
-const Group = styled.div<{ children: ReactNode[]; $gap?: string }>`
- > div:first-of-type {
- label {
- width: calc(${({ children }) => children.length} * 100%);
- }
- }
-
- .inputGroupEnd {
- & > div {
- border-left-width: 0;
- }
- margin-left: 0;
- }
-
- .inputGroupStart {
- & > div {
- border-right-width: 0;
- }
-
- & + .inputGroupEnd > div {
- border-left-width: 2px;
- }
-
- margin-right: 0;
- }
-
- display: grid;
- grid-template-columns: repeat(${({ children }) => children.length}, 1fr);
- ${({ $gap }) => $gap && `gap: ${$gap};`}
-`;
-
-const ButtonContainer = styled.div`
- margin: var(--spacing-m) 0;
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
- gap: var(--spacing-m);
-`;
-
-const SubmitButton = styled(MediumButton)`
- width: 100%;
-
- @media (min-width: ${breakpoints.s}) {
- width: auto;
- white-space: nowrap;
- }
-`;
+import SingleLabelInputGroup from "../common/SingleLabelInputGroup";
const filterOrder = [
"applicationRound",
@@ -137,7 +24,7 @@ const filterOrder = [
"reservationUnitTypes",
"unit",
"purposes",
-];
+] as const;
type FormValues = {
minPersons: number | null;
@@ -176,7 +63,6 @@ export function SeasonalSearchForm({
}): JSX.Element | null {
const { t } = useTranslation();
- const [areFiltersVisible, setAreFiltersVisible] = useState(false);
const { handleSearch } = useSearchModify();
const searchValues = useSearchValues();
@@ -207,106 +93,82 @@ export function SeasonalSearchForm({
}
};
- const multiSelectFilters = ["unit", "reservationUnitTypes", "purposes"];
- const hideList = ["id", "order", "sort", "ref"];
+ const multiSelectFilters = [
+ "unit",
+ "reservationUnitTypes",
+ "purposes",
+ ] as const;
+ const hideList = ["id", "order", "sort", "ref"] as const;
return (
);
}
diff --git a/apps/ui/components/search/SingleSearchForm.tsx b/apps/ui/components/search/SingleSearchForm.tsx
index 3861c1c1cd..381918db6d 100644
--- a/apps/ui/components/search/SingleSearchForm.tsx
+++ b/apps/ui/components/search/SingleSearchForm.tsx
@@ -3,14 +3,11 @@ import { useTranslation } from "next-i18next";
import { IconSearch, TextInput } from "hds-react";
import { type SubmitHandler, useForm, Controller } from "react-hook-form";
import styled from "styled-components";
-import { breakpoints } from "common/src/common/style";
import { addYears, startOfDay } from "date-fns";
-import { ShowAllContainer } from "common/src/components";
import { TimeRangePicker } from "common/src/components/form";
import { toUIDate } from "common/src/common/util";
import { fromUIDate } from "@/modules/util";
import { getDurationOptions, participantCountOptions } from "@/modules/const";
-import { MediumButton } from "@/styles/util";
import { Checkbox, DateRangePicker } from "@/components/form";
import { FilterTagList } from "./FilterTagList";
import SingleLabelInputGroup from "@/components/common/SingleLabelInputGroup";
@@ -24,112 +21,16 @@ import {
mapSingleBooleanParamToFormValue,
mapSingleParamToFormValue,
} from "@/modules/search";
-
-const TopContainer = styled.div`
- display: flex;
- flex-flow: column nowrap;
- gap: var(--spacing-m);
-
- @media (min-width: ${breakpoints.m}) {
- grid-template-columns: 1fr 154px;
- }
-`;
-
-const Filters = styled.div`
- margin-top: 0;
- max-width: 100%;
- display: grid;
- grid-template-columns: auto;
- grid-template-rows: repeat(2, auto);
- grid-gap: var(--spacing-m);
- font-size: var(--fontsize-body-m);
- > div {
- grid-column: 1 / span 3;
- }
-
- label {
- font-family: var(--font-medium);
- font-weight: 500;
- }
-
- @media (min-width: ${breakpoints.m}) {
- margin-top: var(--spacing-s);
- grid-template-columns: repeat(3, auto);
- > div {
- grid-column: span 1;
- }
- }
-
- @media (min-width: ${breakpoints.l}) {
- grid-template-columns: repeat(3, 1fr);
- }
-`;
+import {
+ BottomContainer,
+ Filters,
+ OptionalFilters,
+ StyledSubmitButton,
+} from "./styled";
const StyledCheckBox = styled(Checkbox)`
- &&& {
- @media (min-width: ${breakpoints.m}) {
- margin-top: calc(-70px + var(--spacing-layout-2-xs));
- grid-column: 3 / span 1;
- grid-row: 4;
- }
- }
-`;
-
-const OptionalFilters = styled(ShowAllContainer)`
- && {
- grid-column: 1 / span 3;
- }
- > [class="ShowAllContainer__Content"] {
- display: grid;
- grid-template-columns: 1fr;
- gap: var(--spacing-m);
- &:empty {
- row-gap: 0;
- }
- }
- /* If OptionalFilters is closed (== has no children), remove the row-gap and margin-top from the
- toggle button container. Otherwise the toggle button container will have an unwanted gap above it
- resulting from the empty grid row in breakpoints larger than mobile/s. */
- @media (min-width: ${breakpoints.m}) {
- grid-column: 1 / span 2;
- grid-row: 3 / span 1;
- > [class*="ShowAllContainer__ToggleButtonContainer"] {
- margin-top: var(--spacing-s);
- }
- > [class="ShowAllContainer__Content"] {
- grid-template-columns: repeat(2, 1fr);
- gap: var(--spacing-m);
- &:empty {
- row-gap: 0;
- ~ [class*="ShowAllContainer__ToggleButtonContainer"] {
- margin-top: 0;
- }
- }
- }
-
- > div {
- grid-column: span 1;
- }
- }
- @media (min-width: ${breakpoints.l}) {
- > [class*="ShowAllContainer__Content"] {
- grid-template-columns: repeat(3, 1fr);
- grid-column: 1 / span 3;
- }
- }
-`;
-
-const BottomContainer = styled.div`
- margin: var(--spacing-m) 0;
- display: flex;
- width: 100%;
- flex-flow: column nowrap;
- align-items: flex-start;
- justify-content: space-between;
- gap: var(--spacing-m);
- @media (min-width: ${breakpoints.m}) {
- flex-flow: row nowrap;
- }
+ margin: 0 !important;
+ grid-column: -2 / span 1;
`;
const SingleLabelRangeWrapper = styled(SingleLabelInputGroup)<{
@@ -141,14 +42,6 @@ const SingleLabelRangeWrapper = styled(SingleLabelInputGroup)<{
}
`;
-const SubmitButton = styled(MediumButton)`
- width: 100%;
- @media (min-width: ${breakpoints.s}) {
- width: 120px;
- white-space: nowrap;
- }
-`;
-
type FormValues = {
// TODO there is some confusion on the types of these
// they are actually an array of pks (number) but they are encoded as val1,val2,val3 string
@@ -288,136 +181,134 @@ export function SingleSearchForm({
return (
);
diff --git a/apps/ui/components/search/SingleSearchReservationUnitCard.tsx b/apps/ui/components/search/SingleSearchReservationUnitCard.tsx
index b2ebcef0f4..c8b7dd4750 100644
--- a/apps/ui/components/search/SingleSearchReservationUnitCard.tsx
+++ b/apps/ui/components/search/SingleSearchReservationUnitCard.tsx
@@ -166,7 +166,7 @@ function ReservationUnitCard({ reservationUnit }: PropsT): JSX.Element {
}
const buttons = [
-
+
{t("common:show")}
,
diff --git a/apps/ui/components/search/styled.ts b/apps/ui/components/search/styled.ts
new file mode 100644
index 0000000000..0ab10c8ed5
--- /dev/null
+++ b/apps/ui/components/search/styled.ts
@@ -0,0 +1,61 @@
+import styled from "styled-components";
+import { breakpoints, fontMedium } from "common";
+import {
+ ShowAllContainer,
+ type ShowAllContainerProps,
+} from "common/src/components";
+import { Flex } from "common/styles/util";
+import { SubmitButton } from "@/styles/util";
+
+/// Filter container using responsive grid
+export const Filters = styled.div`
+ grid-gap: var(--spacing-m);
+
+ /* TODO this should be a default value or a separate styled component */
+ label {
+ ${fontMedium}
+ }
+
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
+ align-items: baseline;
+ gap: var(--spacing-m);
+`;
+
+/// Optional filters (behind an accordion toggle) with responsive grid for content
+/// bit complex css because the component wasn't designed for this use case
+export const OptionalFilters = styled(ShowAllContainer)`
+ && {
+ grid-column: 1 / -1;
+ grid-template-columns: auto;
+ display: grid;
+ }
+ & > div {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
+ align-items: baseline;
+ gap: var(--spacing-s);
+ }
+`;
+
+export const BottomContainer = styled(Flex).attrs({
+ $justify: "space-between",
+ $align: "center",
+})`
+ /* TODO have to use this or add gap to the parent
+ * there is an issue with the grid above on desktop (not mobile) having an extra gap
+ */
+ margin-top: var(--spacing-m);
+ /* have to use flex-flow: otherwise on desktop the button will be split to the second line */
+ flex-flow: column nowrap;
+ @media (min-width: ${breakpoints.m}) {
+ flex-flow: row nowrap;
+ }
+`;
+
+// Have to set max-width so this doesn't grow inside a flex container
+export const StyledSubmitButton = styled(SubmitButton)`
+ @media (min-width: ${breakpoints.s}) {
+ max-width: fit-content;
+ }
+`;
diff --git a/apps/ui/gql/gql-types.ts b/apps/ui/gql/gql-types.ts
index 93a6f667cc..e11cafa6a4 100644
--- a/apps/ui/gql/gql-types.ts
+++ b/apps/ui/gql/gql-types.ts
@@ -6219,7 +6219,7 @@ export type ListReservationsQuery = {
price?: string | null;
paymentOrder: Array<{
id: string;
- orderUuid?: string | null;
+ checkoutUrl?: string | null;
expiresInMinutes?: number | null;
status?: OrderStatus | null;
}>;
@@ -6283,6 +6283,27 @@ export type ReservationInfoFragment = {
homeCity?: { id: string; pk?: number | null; name: string } | null;
};
+export type OrderFieldsFragment = {
+ id: string;
+ reservationPk?: string | null;
+ status?: OrderStatus | null;
+ paymentType: PaymentType;
+ receiptUrl?: string | null;
+ checkoutUrl?: string | null;
+};
+
+export type ReservationStateQueryVariables = Exact<{
+ id: Scalars["ID"]["input"];
+}>;
+
+export type ReservationStateQuery = {
+ reservation?: {
+ id: string;
+ pk?: number | null;
+ state?: ReservationStateChoice | null;
+ } | null;
+};
+
export type ReservationQueryVariables = Exact<{
id: Scalars["ID"]["input"];
}>;
@@ -6327,8 +6348,11 @@ export type ReservationQuery = {
user?: { id: string; email: string; pk?: number | null } | null;
paymentOrder: Array<{
id: string;
- orderUuid?: string | null;
+ reservationPk?: string | null;
status?: OrderStatus | null;
+ paymentType: PaymentType;
+ receiptUrl?: string | null;
+ checkoutUrl?: string | null;
}>;
reservationUnits: Array<{
id: string;
@@ -8194,6 +8218,16 @@ export const ReservationInfoFragmentDoc = gql`
numPersons
}
`;
+export const OrderFieldsFragmentDoc = gql`
+ fragment OrderFields on PaymentOrderNode {
+ id
+ reservationPk
+ status
+ paymentType
+ receiptUrl
+ checkoutUrl
+ }
+`;
export const LocationFieldsFragmentDoc = gql`
fragment LocationFields on LocationNode {
id
@@ -9972,9 +10006,8 @@ export const ListReservationsDocument = gql`
...ReservationOrderStatus
paymentOrder {
id
- orderUuid
+ checkoutUrl
expiresInMinutes
- status
}
isBlocked
reservationUnits {
@@ -10070,6 +10103,90 @@ export type ListReservationsQueryResult = Apollo.QueryResult<
ListReservationsQuery,
ListReservationsQueryVariables
>;
+export const ReservationStateDocument = gql`
+ query ReservationState($id: ID!) {
+ reservation(id: $id) {
+ id
+ pk
+ state
+ }
+ }
+`;
+
+/**
+ * __useReservationStateQuery__
+ *
+ * To run a query within a React component, call `useReservationStateQuery` and pass it any options that fit your needs.
+ * When your component renders, `useReservationStateQuery` returns an object from Apollo Client that contains loading, error, and data properties
+ * you can use to render your UI.
+ *
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
+ *
+ * @example
+ * const { data, loading, error } = useReservationStateQuery({
+ * variables: {
+ * id: // value for 'id'
+ * },
+ * });
+ */
+export function useReservationStateQuery(
+ baseOptions: Apollo.QueryHookOptions<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+ > &
+ (
+ | { variables: ReservationStateQueryVariables; skip?: boolean }
+ | { skip: boolean }
+ )
+) {
+ const options = { ...defaultOptions, ...baseOptions };
+ return Apollo.useQuery(
+ ReservationStateDocument,
+ options
+ );
+}
+export function useReservationStateLazyQuery(
+ baseOptions?: Apollo.LazyQueryHookOptions<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+ >
+) {
+ const options = { ...defaultOptions, ...baseOptions };
+ return Apollo.useLazyQuery<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+ >(ReservationStateDocument, options);
+}
+export function useReservationStateSuspenseQuery(
+ baseOptions?:
+ | Apollo.SkipToken
+ | Apollo.SuspenseQueryHookOptions<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+ >
+) {
+ const options =
+ baseOptions === Apollo.skipToken
+ ? baseOptions
+ : { ...defaultOptions, ...baseOptions };
+ return Apollo.useSuspenseQuery<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+ >(ReservationStateDocument, options);
+}
+export type ReservationStateQueryHookResult = ReturnType<
+ typeof useReservationStateQuery
+>;
+export type ReservationStateLazyQueryHookResult = ReturnType<
+ typeof useReservationStateLazyQuery
+>;
+export type ReservationStateSuspenseQueryHookResult = ReturnType<
+ typeof useReservationStateSuspenseQuery
+>;
+export type ReservationStateQueryResult = Apollo.QueryResult<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+>;
export const ReservationDocument = gql`
query Reservation($id: ID!) {
reservation(id: $id) {
@@ -10096,9 +10213,7 @@ export const ReservationDocument = gql`
priceNet
taxPercentageValue
paymentOrder {
- id
- orderUuid
- status
+ ...OrderFields
}
reservationUnits {
id
@@ -10112,6 +10227,7 @@ export const ReservationDocument = gql`
${ReserveeNameFieldsFragmentDoc}
${ReserveeBillingFieldsFragmentDoc}
${ReservationInfoFragmentDoc}
+ ${OrderFieldsFragmentDoc}
${ReservationUnitFieldsFragmentDoc}
${CancellationRuleFieldsFragmentDoc}
`;
@@ -10329,14 +10445,10 @@ export type AdjustReservationTimeMutationOptions = Apollo.BaseMutationOptions<
export const OrderDocument = gql`
query Order($orderUuid: String!) {
order(orderUuid: $orderUuid) {
- id
- reservationPk
- status
- paymentType
- receiptUrl
- checkoutUrl
+ ...OrderFields
}
}
+ ${OrderFieldsFragmentDoc}
`;
/**
diff --git a/apps/ui/hooks/reservation.tsx b/apps/ui/hooks/reservation.tsx
deleted file mode 100644
index 6fcb52c14a..0000000000
--- a/apps/ui/hooks/reservation.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { useState } from "react";
-import { type ApolloError } from "@apollo/client";
-import {
- type PaymentOrderNode,
- OrderStatus,
- useDeleteReservationMutation,
- useRefreshOrderMutation,
- useOrderQuery,
-} from "@gql/gql-types";
-
-function convertOrderStatus(status: string): OrderStatus | undefined {
- switch (status) {
- case OrderStatus.Cancelled:
- return OrderStatus.Cancelled;
- case OrderStatus.Draft:
- return OrderStatus.Draft;
- case OrderStatus.Expired:
- return OrderStatus.Expired;
- case OrderStatus.Paid:
- return OrderStatus.Paid;
- case OrderStatus.PaidManually:
- return OrderStatus.PaidManually;
- case OrderStatus.Refunded:
- return OrderStatus.Refunded;
- default:
- return undefined;
- }
-}
-
-type UseOrderProps = {
- orderUuid?: string;
-};
-
-export function useOrder({ orderUuid }: UseOrderProps): {
- order?: PaymentOrderNode;
- isError: boolean;
- refreshError?: ApolloError;
- isLoading: boolean;
- refresh: () => void;
- called: boolean;
-} {
- const [data, setData] = useState(undefined);
- const [called, setCalled] = useState(false);
-
- const { error, loading: orderLoading } = useOrderQuery({
- fetchPolicy: "no-cache",
- skip: !orderUuid,
- variables: { orderUuid: orderUuid ?? "" },
- onCompleted: (res) => {
- setCalled(true);
- setData(res?.order ?? undefined);
- },
- onError: () => {
- setCalled(true);
- },
- });
-
- const [refresh, { error: refreshError, loading: refreshLoading }] =
- useRefreshOrderMutation({
- fetchPolicy: "no-cache",
- variables: { input: { orderUuid: orderUuid ?? "" } },
- onCompleted: (res) => {
- if (data != null && res.refreshOrder?.status != null) {
- const status = convertOrderStatus(res.refreshOrder.status);
- setData({ ...data, status });
- } else {
- setData(undefined);
- }
- },
- // catch all thrown errors so we don't crash
- // particularly there is an EXTERNAL_SERVICE_ERROR that happens occasionally
- onError: () => {},
- });
-
- return {
- order: data ?? undefined,
- isError: error != null,
- refreshError,
- isLoading: orderLoading || refreshLoading,
- refresh,
- called,
- };
-}
-
-export function useDeleteReservation() {
- const [mutation, { data, error, loading }] = useDeleteReservationMutation();
-
- const deleted = data?.deleteReservation?.deleted ?? false;
-
- return {
- mutation,
- error,
- isLoading: loading,
- deleted,
- };
-}
diff --git a/apps/ui/hooks/useDropdownKeyboardNavigation.tsx b/apps/ui/hooks/useDropdownKeyboardNavigation.tsx
deleted file mode 100644
index b78d5ce8b6..0000000000
--- a/apps/ui/hooks/useDropdownKeyboardNavigation.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import React, { useCallback } from "react";
-
-interface KeyboardNavigationProps {
- container: React.MutableRefObject;
- listLength: number;
- initialFocusedIndex?: number;
- onKeyDown?: (event: KeyboardEvent) => void;
-}
-
-interface DropdownKeyboardNavigationState {
- focusedIndex: number;
- setFocusedIndex: (index: number) => void;
- setup: () => void;
- teardown: () => void;
-}
-
-const useDropdownKeyboardNavigation = ({
- container,
- listLength,
- initialFocusedIndex,
- onKeyDown,
-}: KeyboardNavigationProps): DropdownKeyboardNavigationState => {
- const [focusedIndex, setFocusedIndex] = React.useState(-1);
- const [isInitialNavigation, setIsInitialNavigation] = React.useState(true);
- const isStartingPosition = focusedIndex === -1;
-
- const focusOption = React.useCallback(
- (direction: "down" | "up", index: number) => {
- switch (direction) {
- case "down":
- setFocusedIndex(index < listLength - 1 ? index + 1 : 0);
- break;
- case "up":
- setFocusedIndex(index > 0 ? index - 1 : listLength - 1);
- break;
- default:
- }
- setIsInitialNavigation(false);
- },
- [listLength]
- );
-
- const isComponentFocused = useCallback(() => {
- const active = document.activeElement;
- const current = container && container.current;
-
- if (current && active instanceof Node && current.contains(active)) {
- return true;
- }
- return false;
- }, [container]);
-
- const handleKeyDown = React.useCallback(
- (event: KeyboardEvent) => {
- // Handle keyboard events only if current element is focused
- if (!isComponentFocused()) return;
-
- if (onKeyDown) {
- onKeyDown(event);
- }
- switch (event.key) {
- case "ArrowUp":
- if (isInitialNavigation && typeof initialFocusedIndex === "number") {
- focusOption("up", initialFocusedIndex);
- } else if (isStartingPosition) {
- setFocusedIndex(listLength - 1);
- } else {
- focusOption("up", focusedIndex);
- }
- event.preventDefault();
- break;
- case "ArrowDown":
- if (isInitialNavigation && typeof initialFocusedIndex === "number") {
- focusOption("down", initialFocusedIndex - 1);
- } else {
- focusOption("down", focusedIndex);
- }
- event.preventDefault();
- break;
- case "Escape":
- setFocusedIndex(-1);
- event.preventDefault();
- break;
- default:
- }
- },
- [
- focusOption,
- focusedIndex,
- initialFocusedIndex,
- isComponentFocused,
- isInitialNavigation,
- isStartingPosition,
- listLength,
- onKeyDown,
- ]
- );
-
- const setup = React.useCallback(() => {
- document.addEventListener("keydown", handleKeyDown);
- }, [handleKeyDown]);
-
- const teardown = React.useCallback(() => {
- document.removeEventListener("keydown", handleKeyDown);
- }, [handleKeyDown]);
-
- React.useEffect(() => {
- setIsInitialNavigation(true);
- }, [initialFocusedIndex]);
-
- React.useEffect(() => {
- // Whenever the list changes, reset focused index
- setFocusedIndex(-1);
- }, [listLength]);
-
- return { focusedIndex, setFocusedIndex, setup, teardown };
-};
-
-export default useDropdownKeyboardNavigation;
diff --git a/apps/ui/hooks/useSearchValues.tsx b/apps/ui/hooks/useSearchValues.tsx
index 3fe19ef058..ca92655a26 100644
--- a/apps/ui/hooks/useSearchValues.tsx
+++ b/apps/ui/hooks/useSearchValues.tsx
@@ -52,7 +52,7 @@ export function useSearchModify() {
};
/// @param hideList - list of keys to ignore when resetting the query
- const handleResetTags = (hideList: string[]) => {
+ const handleResetTags = (hideList: readonly string[]) => {
const keys = Object.keys(searchValues);
const newValues = keys.reduce((acc, key) => {
if (hideList.includes(key)) {
diff --git a/apps/ui/modules/__tests__/util.test.ts b/apps/ui/modules/__tests__/util.test.ts
index e8437cb952..4a09fcabd4 100644
--- a/apps/ui/modules/__tests__/util.test.ts
+++ b/apps/ui/modules/__tests__/util.test.ts
@@ -1,9 +1,5 @@
import { ApolloError } from "@apollo/client";
-import {
- getComboboxValues,
- getReadableList,
- printErrorMessages,
-} from "../util";
+import { getReadableList, printErrorMessages } from "../util";
jest.mock("next-i18next", () => ({
i18n: {
@@ -14,23 +10,6 @@ jest.mock("next-i18next", () => ({
},
}));
-test("getComboboxValues", () => {
- const optionsAbc = [
- { label: "a", value: "a" },
- { label: "b", value: "b" },
- { label: "c", value: "c" },
- ];
-
- expect(getComboboxValues("b,c", optionsAbc)).toEqual([
- { label: "b", value: "b" },
- { label: "c", value: "c" },
- ]);
-
- expect(getComboboxValues("", optionsAbc)).toEqual([]);
- expect(getComboboxValues("b,c", [])).toEqual([]);
- expect(getComboboxValues("", [])).toEqual([]);
-});
-
test("getReadableList", () => {
expect(getReadableList(["a"])).toEqual("a");
expect(getReadableList(["a", "b"])).toEqual("a common:and b");
diff --git a/apps/ui/modules/queries/reservation.tsx b/apps/ui/modules/queries/reservation.tsx
index e3b5678e5f..892ff1df15 100644
--- a/apps/ui/modules/queries/reservation.tsx
+++ b/apps/ui/modules/queries/reservation.tsx
@@ -115,9 +115,8 @@ export const LIST_RESERVATIONS = gql`
...ReservationOrderStatus
paymentOrder {
id
- orderUuid
+ checkoutUrl
expiresInMinutes
- status
}
isBlocked
reservationUnits {
@@ -156,6 +155,27 @@ const RESERVATION_INFO_FRAGMENT = gql`
}
`;
+export const ORDER_FRAGMENT = gql`
+ fragment OrderFields on PaymentOrderNode {
+ id
+ reservationPk
+ status
+ paymentType
+ receiptUrl
+ checkoutUrl
+ }
+`;
+
+export const GET_RESERVATION_STATE = gql`
+ query ReservationState($id: ID!) {
+ reservation(id: $id) {
+ id
+ pk
+ state
+ }
+ }
+`;
+
// TODO do we need all the fields from ReservationUnitNode? ex. pricing (since we should be using the Reservations own pricing anyway)
// TODO can we split this into smaller queries? per case?
// making a reservation, showing a reservation, editing a reservation, cancelling a reservation
@@ -191,9 +211,7 @@ export const GET_RESERVATION = gql`
priceNet
taxPercentageValue
paymentOrder {
- id
- orderUuid
- status
+ ...OrderFields
}
reservationUnits {
id
@@ -238,12 +256,7 @@ export const ADJUST_RESERVATION_TIME = gql`
export const GET_ORDER = gql`
query Order($orderUuid: String!) {
order(orderUuid: $orderUuid) {
- id
- reservationPk
- status
- paymentType
- receiptUrl
- checkoutUrl
+ ...OrderFields
}
}
`;
diff --git a/apps/ui/modules/reservationUnit.ts b/apps/ui/modules/reservationUnit.ts
index 9a9c363369..0c261ce7a2 100644
--- a/apps/ui/modules/reservationUnit.ts
+++ b/apps/ui/modules/reservationUnit.ts
@@ -169,9 +169,13 @@ export function getUnitName(
);
}
+type InstructionsKey =
+ | "reservationPendingInstructions"
+ | "reservationCancelledInstructions"
+ | "reservationConfirmedInstructions";
export function getReservationUnitInstructionsKey(
- state?: ReservationStateChoice | null | undefined
-): string | null {
+ state: Maybe | undefined
+): InstructionsKey | null {
switch (state) {
case ReservationStateChoice.Created:
case ReservationStateChoice.RequiresHandling:
diff --git a/apps/ui/modules/serverUtils.ts b/apps/ui/modules/serverUtils.ts
index 85466300fa..f721940fdc 100644
--- a/apps/ui/modules/serverUtils.ts
+++ b/apps/ui/modules/serverUtils.ts
@@ -1,12 +1,19 @@
import { env } from "@/env.mjs";
-import { type ApolloClient } from "@apollo/client";
+import { NormalizedCacheObject, type ApolloClient } from "@apollo/client";
import {
TermsType,
TermsOfUseDocument,
type TermsOfUseQuery,
type TermsOfUseQueryVariables,
+ type ReservationStateQuery,
+ type OrderQuery,
+ type OrderQueryVariables,
+ OrderDocument,
+ type ReservationStateQueryVariables,
+ ReservationStateDocument,
} from "@gql/gql-types";
import { genericTermsVariant } from "./const";
+import { base64encode } from "common/src/helpers";
export function getVersion(): string {
return (
@@ -68,3 +75,36 @@ export async function getGenericTerms(apolloClient: ApolloClient) {
return tos;
}
+
+// TODO narrow down the errors properly and show the user the real reason
+// requires refactoring error pages to display GQL errors
+export async function getReservationByOrderUuid(
+ apolloClient: ApolloClient,
+ uuid: string
+): Promise | null> {
+ // TODO retry once if not found (or increase the timeout so the webhook from store has fired)
+ const { data: orderData } = await apolloClient.query<
+ OrderQuery,
+ OrderQueryVariables
+ >({
+ query: OrderDocument,
+ variables: { orderUuid: uuid },
+ });
+
+ const order = orderData?.order ?? undefined;
+ const { reservationPk: pk } = order ?? {};
+ if (!pk) {
+ return null;
+ }
+
+ const id = base64encode(`ReservationNode:${pk}`);
+ const { data } = await apolloClient.query<
+ ReservationStateQuery,
+ ReservationStateQueryVariables
+ >({
+ query: ReservationStateDocument,
+ variables: { id },
+ });
+
+ return data?.reservation ?? null;
+}
diff --git a/apps/ui/modules/style.ts b/apps/ui/modules/style.ts
deleted file mode 100644
index d6dcef8dec..0000000000
--- a/apps/ui/modules/style.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Koros } from "hds-react";
-import styled from "styled-components";
-
-export const StyledKoros = styled(Koros)<{
- $from: string;
- $to: string;
-}>`
- ${({ $from, $to }) => `
- background-color: ${$from};
- fill: ${$to};
- `}
-
- margin-bottom: -1px;
-`;
diff --git a/apps/ui/modules/urls.ts b/apps/ui/modules/urls.ts
index e9f4033429..8952141868 100644
--- a/apps/ui/modules/urls.ts
+++ b/apps/ui/modules/urls.ts
@@ -9,30 +9,15 @@ export const seasonalPrefix = "/recurring";
export const applicationsPath = `${applicationsPrefix}/`;
export const reservationsPath = `${reservationsPrefix}/`;
+type ApplicationRoundPages = "criteria";
export function getApplicationRoundPath(
- id: Maybe | undefined,
- page?: string | undefined
-): string {
- if (id == null) {
- return "";
- }
- return `${seasonalPrefix}/${id}/${page ?? ""}`;
-}
-
-export function getSeasonalSearchPath(
pk: Maybe | undefined,
- params?: URLSearchParams
+ page?: ApplicationRoundPages | undefined
): string {
if (pk == null) {
return "";
}
- const base = `${seasonalPrefix}/${pk}`;
-
- if (params && Object.keys(params).length > 0) {
- return `${base}?${params.toString()}`;
- }
-
- return base;
+ return `${seasonalPrefix}/${pk}/${page ?? ""}`;
}
export function getSingleSearchPath(params?: URLSearchParams): string {
@@ -72,9 +57,10 @@ export function getApplicationSectionPath(
return `${getApplicationPath(applicationPk, "view")}/${sectionPk}`;
}
+type ReservationPages = "cancel" | "edit" | "confirmation";
export function getReservationPath(
pk: Maybe | undefined,
- page?: string | undefined
+ page?: ReservationPages | undefined
): string {
if (pk == null) {
return "";
diff --git a/apps/ui/modules/util.ts b/apps/ui/modules/util.ts
index 1040fcb7fb..e0c97a2dbd 100644
--- a/apps/ui/modules/util.ts
+++ b/apps/ui/modules/util.ts
@@ -2,7 +2,6 @@ import { isSameDay, parseISO } from "date-fns";
import { i18n, TFunction } from "next-i18next";
import { trim } from "lodash";
import type { ApolloError } from "@apollo/client";
-import type { OptionType } from "common";
import {
toApiDate,
toUIDate,
@@ -10,11 +9,7 @@ import {
fromApiDate as fromAPIDate,
fromUIDate,
} from "common/src/common/util";
-import type {
- AgeGroupNode,
- ImageFragment,
- LocationFieldsI18nFragment,
-} from "@gql/gql-types";
+import type { ImageFragment, LocationFieldsI18nFragment } from "@gql/gql-types";
import { isBrowser } from "./const";
import { type LocalizationLanguages } from "common/src/helpers";
@@ -97,51 +92,6 @@ function getLabel(
export { getLabel as getParameterLabel };
-/// @deprecated - OptionType is dangerous, union types break type safety in comparisons
-export const mapOptions = (
- src: ParameterType[] | AgeGroupNode[],
- emptyOptionLabel?: string,
- lang: LocalizationLanguages = "fi"
-): OptionType[] => {
- const r: OptionType[] = [
- ...(emptyOptionLabel ? [{ label: emptyOptionLabel, value: 0 }] : []),
- ...src.map((v) => ({
- label: getLabel(v, lang),
- value: v.pk ?? 0,
- })),
- ];
- return r;
-};
-
-export const getSelectedOption = (
- selectedId: number | string | null,
- options: OptionType[]
-): OptionType | null => {
- const selected = String(selectedId);
- const option = options.find((o) => String(o.value) === selected);
- return option ?? null;
-};
-
-export const getComboboxValues = (
- value: string,
- options: OptionType[]
-): OptionType[] => {
- if (value.length === 0) {
- return [];
- }
- if (value.includes(",")) {
- return value
- .split(",")
- .map((unit) => getSelectedOption(unit, options))
- .filter((x): x is OptionType => x != null);
- }
- const val = getSelectedOption(value, options);
- if (val) {
- return [val];
- }
- return [];
-};
-
const imagePriority = ["main", "map", "ground_plan", "other"].map((n) =>
n.toUpperCase()
);
diff --git a/apps/ui/next.config.mjs b/apps/ui/next.config.mjs
index accbf94559..7381291a44 100644
--- a/apps/ui/next.config.mjs
+++ b/apps/ui/next.config.mjs
@@ -31,6 +31,14 @@ const nextConfig = {
},
i18n,
basePath: env.NEXT_PUBLIC_BASE_URL,
+ async rewrites() {
+ return [
+ {
+ source: "/reservation/confirmation/:id",
+ destination: "/reservations/:id/confirmation",
+ },
+ ];
+ },
compiler: {
styledComponents: {
ssr: true,
diff --git a/apps/ui/pages/404.tsx b/apps/ui/pages/404.tsx
index 45fc3956bd..a7eb4df374 100644
--- a/apps/ui/pages/404.tsx
+++ b/apps/ui/pages/404.tsx
@@ -3,7 +3,6 @@ import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import styled from "styled-components";
import { useTranslation } from "next-i18next";
-import { CenteredContainer } from "common/src/layout/Container";
import { getCommonServerSideProps } from "@/modules/serverUtils";
/// next doesn't allow getServersideProps in 404.tsx (you have to use app router for that)
@@ -24,6 +23,7 @@ type Props = {
body?: string;
};
+// maybe we want to center this?
const Wrapper = styled.div`
padding: var(--spacing-layout-xl) 0;
`;
@@ -33,10 +33,8 @@ function Page404({ title, body }: Props): JSX.Element {
return (
-
- {title || "404"}
- {body || t("404.body")}
-
+ {title || "404"}
+ {body || t("404.body")}
);
}
diff --git a/apps/ui/pages/500.tsx b/apps/ui/pages/500.tsx
index efc82fbd0d..c4d44cfe15 100644
--- a/apps/ui/pages/500.tsx
+++ b/apps/ui/pages/500.tsx
@@ -3,9 +3,10 @@ import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import styled from "styled-components";
import { useTranslation } from "next-i18next";
-import { CenteredContainer } from "common/src/layout/Container";
import { getCommonServerSideProps } from "@/modules/serverUtils";
+// TODO this is a copy of 404.tsx, but with 500 instead of 404
+
/// next doesn't allow getServersideProps in 500.tsx (you have to use app router for that)
export async function getStaticProps({ locale }: GetServerSidePropsContext) {
return {
@@ -25,10 +26,8 @@ function Page500(): JSX.Element {
return (
-
- 500
- {t("500.body")}
-
+ 500
+ {t("500.body")}
);
}
diff --git a/apps/ui/pages/_app.tsx b/apps/ui/pages/_app.tsx
index a02d6ae2fc..64ef50768b 100644
--- a/apps/ui/pages/_app.tsx
+++ b/apps/ui/pages/_app.tsx
@@ -9,7 +9,7 @@ import { ExternalScripts } from "@/components/ExternalScripts";
import { DataContextProvider } from "@/context/DataContext";
import { createApolloClient } from "@/modules/apolloClient";
import { TrackingWrapper } from "@/modules/tracking";
-import "common/styles/variables.css";
+import "common/styles/global.scss";
import "../styles/global.scss";
import { updateSentryConfig } from "@/sentry.client.config";
import { ToastContainer } from "common/src/common/toast";
@@ -40,8 +40,8 @@ function MyApp({ Component, pageProps }: AppProps) {
-
+
diff --git a/apps/ui/pages/applications/[id]/sent.tsx b/apps/ui/pages/applications/[id]/sent.tsx
index 342b16e87f..68f7595a40 100644
--- a/apps/ui/pages/applications/[id]/sent.tsx
+++ b/apps/ui/pages/applications/[id]/sent.tsx
@@ -1,15 +1,16 @@
-import { Button, IconAngleRight } from "hds-react";
-import { useRouter } from "next/router";
+import { IconAngleRight } from "hds-react";
import React from "react";
import { useTranslation } from "next-i18next";
import styled from "styled-components";
import { breakpoints } from "common/src/common/style";
-import { Container } from "common";
+import { H1 } from "common";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import type { GetServerSidePropsContext } from "next";
import { applicationsPath } from "@/modules/urls";
-import { Head } from "@/components/application/Head";
import { getCommonServerSideProps } from "@/modules/serverUtils";
+import { FontMedium } from "@/components/application/styled";
+import BreadcrumbWrapper from "@/components/common/BreadcrumbWrapper";
+import { ButtonLikeLink } from "@/components/common/ButtonLikeLink";
const Paragraph = styled.p`
white-space: pre-wrap;
@@ -20,28 +21,20 @@ const Paragraph = styled.p`
}
`;
-const StyledButton = styled(Button)`
- margin-bottom: var(--spacing-layout-l);
-`;
-
function Sent(): JSX.Element {
const { t } = useTranslation();
- const router = useRouter();
return (
-
-
- {t("application:sent.subHeading")}
-
+ <>
+
+ {t("application:sent.heading")}
+ {t("application:sent.subHeading")}
{t("application:sent.body")}
- router.push(applicationsPath)}
- iconRight={}
- size="small"
- >
+
{t("navigation:Item.applications")}
-
-
+
+
+ >
);
}
diff --git a/apps/ui/pages/applications/[id]/view/[section]/index.tsx b/apps/ui/pages/applications/[id]/view/[section]/index.tsx
index 0cb5d4d5bc..2602f9ef99 100644
--- a/apps/ui/pages/applications/[id]/view/[section]/index.tsx
+++ b/apps/ui/pages/applications/[id]/view/[section]/index.tsx
@@ -9,7 +9,7 @@ import {
type ApplicationSectionViewQueryVariables,
ApplicationStatusChoice,
} from "@gql/gql-types";
-import { Container, H2 } from "common";
+import { H1 } from "common";
import { AllReservations } from "@/components/application/ApprovedReservations";
import { gql } from "@apollo/client";
import { useTranslation } from "next-i18next";
@@ -42,11 +42,11 @@ function ViewAll({ applicationSection }: PropsNarrowed): JSX.Element {
},
];
return (
-
-
- {heading}
+ <>
+
+ {heading}
-
+ >
);
}
diff --git a/apps/ui/pages/applications/[id]/view/index.tsx b/apps/ui/pages/applications/[id]/view/index.tsx
index 83f91baae4..2dc1d19c67 100644
--- a/apps/ui/pages/applications/[id]/view/index.tsx
+++ b/apps/ui/pages/applications/[id]/view/index.tsx
@@ -16,11 +16,24 @@ import {
type ApplicationViewQuery,
} from "@gql/gql-types";
import { Tabs } from "hds-react";
-import { Head } from "@/components/application/Head";
-import { Container } from "common";
import { formatDateTime } from "@/modules/util";
-import { ApprovedReservations } from "@/components/application/ApprovedReservations";
+import {
+ ApprovedReservations,
+ BREAKPOINT,
+} from "@/components/application/ApprovedReservations";
import { gql } from "@apollo/client";
+import BreadcrumbWrapper from "@/components/common/BreadcrumbWrapper";
+import { H1 } from "common";
+import styled from "styled-components";
+
+const TabPanel = styled(Tabs.TabPanel)`
+ && {
+ margin-top: var(--spacing-l);
+ @media (width > ${BREAKPOINT}) {
+ margin-top: var(--spacing-xl);
+ }
+ }
+`;
function View({ application, tos }: PropsNarrowed): JSX.Element {
const { t, i18n } = useTranslation();
@@ -41,8 +54,9 @@ function View({ application, tos }: PropsNarrowed): JSX.Element {
application.status === ApplicationStatusChoice.ResultsSent;
return (
-
-
+ <>
+
+ {applicationRoundName}
{showReservations ? (
<>
@@ -58,18 +72,18 @@ function View({ application, tos }: PropsNarrowed): JSX.Element {
{t("application:view.application")}
-
+
-
-
+
+
-
+
>
) : (
)}
-
+ >
);
}
diff --git a/apps/ui/pages/applications/index.tsx b/apps/ui/pages/applications/index.tsx
index 645823d00c..05facbe273 100644
--- a/apps/ui/pages/applications/index.tsx
+++ b/apps/ui/pages/applications/index.tsx
@@ -3,7 +3,6 @@ import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { Notification } from "hds-react";
import { useTranslation } from "next-i18next";
-import styled from "styled-components";
import {
ApplicationStatusChoice,
type ApplicationsQuery,
@@ -15,11 +14,12 @@ import {
ApplicationOrderingChoices,
} from "@gql/gql-types";
import { filterNonNullable } from "common/src/helpers";
-import Head from "@/components/applications/Head";
import ApplicationsGroup from "@/components/applications/ApplicationsGroup";
import { getCommonServerSideProps } from "@/modules/serverUtils";
import { createApolloClient } from "@/modules/apolloClient";
import { useCurrentUser } from "@/hooks/user";
+import { HeroSubheading } from "@/modules/style/typography";
+import { H1 } from "common";
type Props = Awaited>["props"];
type PropsNarrowed = Exclude;
@@ -77,13 +77,6 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
};
}
-const Container = styled.div`
- padding: 0 var(--spacing-m) var(--spacing-m);
- max-width: var(--container-width-xl);
- margin: 0 auto var(--spacing-2-xl) auto;
- font-size: var(--fontsize-body-xl);
-`;
-
function ApplicationGroups({
applications,
actionCallback,
@@ -183,13 +176,18 @@ function ApplicationsPage({
return (
<>
-
-
+
+
+ {t("applications:heading")}
+
+ {t("applications:subHeading")}
+
+
{cancelled && (
>["props"];
-const Home = ({ purposes, units }: Props): JSX.Element => {
+function Home({ purposes, units }: Props): JSX.Element {
const { t } = useTranslation(["home", "common"]);
+ // FIXME add gap to the layout by default (or allow configuring it), need like 48px here
return (
-
-
+
+
-
+
);
-};
+}
-export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
+export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const { locale } = ctx;
const commonProps = getCommonServerSideProps();
const apolloClient = createApolloClient(commonProps.apiBaseUrl, ctx);
@@ -87,6 +88,6 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
])),
},
};
-};
+}
export default Home;
diff --git a/apps/ui/pages/recurring/[id]/criteria/index.tsx b/apps/ui/pages/recurring/[id]/criteria/index.tsx
index 08ecd02de8..ee721f8767 100644
--- a/apps/ui/pages/recurring/[id]/criteria/index.tsx
+++ b/apps/ui/pages/recurring/[id]/criteria/index.tsx
@@ -8,7 +8,7 @@ import {
type ApplicationRoundsUiQuery,
type ApplicationRoundsUiQueryVariables,
} from "@gql/gql-types";
-import { breakpoints, Container, H2 } from "common";
+import { breakpoints, H1 } from "common";
import { createApolloClient } from "@/modules/apolloClient";
import Sanitize from "@/components/common/Sanitize";
import { getTranslation } from "@/modules/util";
@@ -18,6 +18,7 @@ import { getCommonServerSideProps } from "@/modules/serverUtils";
import NotesWhenApplying from "@/components/application/NotesWhenApplying";
type Props = Awaited>["props"];
+type PropsNarrowed = Exclude;
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const { locale, params } = ctx;
@@ -25,6 +26,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const commonProps = getCommonServerSideProps();
const apolloClient = createApolloClient(commonProps.apiBaseUrl, ctx);
+ // TODO use a singular query for this
const { data } = await apolloClient.query<
ApplicationRoundsUiQuery,
ApplicationRoundsUiQueryVariables
@@ -37,25 +39,17 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
.map((n) => n?.node)
.find((n) => n?.pk === id) ?? null;
+ const notFound = applicationRound == null;
return {
- notFound: applicationRound == null,
+ notFound,
props: {
+ ...(notFound ? { notFound } : { applicationRound }),
...commonProps,
- key: `${id}${locale}`,
- applicationRound,
...(await serverSideTranslations(locale ?? "fi")),
},
};
};
-const HeadContent = styled.div`
- max-width: var(--container-width-xl);
- margin: 0 auto var(--spacing-2-xl) auto;
- font-size: var(--fontsize-body-xl);
-`;
-
-const Heading = styled(H2).attrs({ as: "h1" })``;
-
const ContentWrapper = styled.div`
display: flex;
gap: var(--spacing-m);
@@ -64,45 +58,18 @@ const ContentWrapper = styled.div`
}
`;
-const Content = styled.div`
- max-width: var(--container-width-l);
- font-family: var(--font-regular);
- font-size: var(--fontsize-body-l);
-`;
-
-const NotesWrapper = styled.div`
- flex-grow: 1;
-`;
-
-function Criteria({ applicationRound }: Props): JSX.Element | null {
+function Criteria({ applicationRound }: PropsNarrowed): JSX.Element | null {
const { t } = useTranslation();
- if (!applicationRound) {
- return null;
- }
-
+ const title = `${getApplicationRoundName(applicationRound)} ${t("applicationRound:criteria")}`;
return (
<>
-
-
-
-
- {`${getApplicationRoundName(applicationRound)} ${t(
- "applicationRound:criteria"
- )}`}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ {title}
+
+
+
+
>
);
}
diff --git a/apps/ui/pages/recurring/[id]/index.tsx b/apps/ui/pages/recurring/[id]/index.tsx
index 8f3c4c420f..144915c01f 100644
--- a/apps/ui/pages/recurring/[id]/index.tsx
+++ b/apps/ui/pages/recurring/[id]/index.tsx
@@ -4,8 +4,7 @@ import styled from "styled-components";
import type { GetServerSidePropsContext } from "next";
import { Notification } from "hds-react";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
-import { H2 } from "common/src/common/typography";
-import { breakpoints } from "common/src/common/style";
+import { H1 } from "common/src/common/typography";
import {
ApplicationRoundStatusChoice,
ReservationKind,
@@ -17,7 +16,6 @@ import {
type ApplicationRoundsUiQueryVariables,
ApplicationRoundsUiDocument,
} from "@gql/gql-types";
-import { Container } from "common";
import { filterNonNullable } from "common/src/helpers";
import { SeasonalSearchForm } from "@/components/search/SeasonalSearchForm";
import { HeroSubheading } from "@/modules/style/typography";
@@ -81,34 +79,10 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
};
}
-const Wrapper = styled.div`
- margin-bottom: var(--spacing-layout-l);
-`;
-
-const StyledContainer = styled(Container)`
- padding-bottom: var(--spacing-3-xs);
-
- @media (min-width: ${breakpoints.s}) {
- padding-bottom: var(--spacing-2-xs);
- }
-`;
-
-const HeadContainer = styled.div`
- background-color: white;
-
- @media (min-width: ${breakpoints.m}) {
- padding-top: 0;
- }
-`;
-
-const Title = styled(H2).attrs({ as: "h1" })``;
-
+// Uses larger font size
+// TODO should move the default 0 margin to the typography file
const Ingress = styled(HeroSubheading)`
- margin-bottom: var(--spacing-xs);
-`;
-
-const BottomWrapper = styled(Container)`
- padding-top: var(--spacing-l);
+ margin: 0;
`;
function SeasonalSearch({
@@ -150,48 +124,42 @@ function SeasonalSearch({
const pageInfo = currData?.reservationUnits?.pageInfo;
return (
-
+ <>
{error ? (
{t("searchResultList:error")}
) : null}
-
-
-
- {t("search:recurring.heading")}
- {t("search:recurring.text")}
-
+ {t("search:recurring.heading")}
+ {t("search:recurring.text")}
+
+ (
+
-
-
-
- (
-
- ))}
- isLoading={isLoading}
- hasMoreData={query.hasMoreData}
- pageInfo={pageInfo}
- fetchMore={(cursor) => fetchMore(cursor)}
- sortingComponent={}
- />
-
-
-
+ ))}
+ isLoading={isLoading}
+ hasMoreData={query.hasMoreData}
+ pageInfo={pageInfo}
+ fetchMore={(cursor) => fetchMore(cursor)}
+ sortingComponent={}
+ />
+
+ >
);
}
diff --git a/apps/ui/pages/recurring/index.tsx b/apps/ui/pages/recurring/index.tsx
index 0f37e0d922..7df6675134 100644
--- a/apps/ui/pages/recurring/index.tsx
+++ b/apps/ui/pages/recurring/index.tsx
@@ -1,10 +1,8 @@
import React from "react";
-import styled from "styled-components";
import type { GetServerSidePropsContext } from "next";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
-import { H2, H3 } from "common/src/common/typography";
-import { breakpoints } from "common/src/common/style";
+import { H1, H2 } from "common/src/common/typography";
import {
ApplicationRoundOrderingChoices,
ApplicationRoundStatusChoice,
@@ -17,10 +15,11 @@ import { HeroSubheading } from "@/modules/style/typography";
import { ApplicationRoundCard } from "@/components/recurring/ApplicationRoundCard";
import { createApolloClient } from "@/modules/apolloClient";
import { getCommonServerSideProps } from "@/modules/serverUtils";
+import { Flex } from "common/styles/util";
type Props = Awaited>["props"];
-export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
+export async function getServerSideProps(ctx: GetServerSidePropsContext) {
const now = new Date();
const { locale } = ctx;
const commonProps = getCommonServerSideProps();
@@ -55,43 +54,9 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
...(await serverSideTranslations(locale ?? "fi")),
},
};
-};
+}
-const Head = styled.div`
- padding: var(--spacing-m) var(--spacing-m) var(--spacing-xl);
-
- @media (min-width: ${breakpoints.m}) {
- max-width: var(--container-width-xl);
- padding: var(--spacing-m);
- margin: 0 auto;
- padding-bottom: var(--spacing-layout-l);
- }
-`;
-
-const Content = styled.div`
- padding: 0 var(--spacing-m) var(--spacing-xl);
- background-color: var(--color-white);
-
- @media (min-width: ${breakpoints.m}) {
- max-width: var(--container-width-xl);
- margin: 0 auto;
- padding-bottom: var(--spacing-layout-xl);
- }
-`;
-
-const RoundList = styled.div`
- display: flex;
- flex-flow: column nowrap;
- gap: var(--spacing-m);
- margin-bottom: var(--spacing-layout-l);
-`;
-
-const RoundHeading = styled(H3).attrs({ as: "h2" })`
- margin-top: 0;
- margin-bottom: var(--spacing-m);
-`;
-
-const RecurringLander = ({ applicationRounds }: Props): JSX.Element => {
+function RecurringLander({ applicationRounds }: Props): JSX.Element {
const { t } = useTranslation();
const activeApplicationRounds = applicationRounds.filter(
@@ -110,60 +75,54 @@ const RecurringLander = ({ applicationRounds }: Props): JSX.Element => {
return (
<>
-
- {t("recurringLander:heading")}
+
+
+ {t("recurringLander:heading")}
+
{t("recurringLander:subHeading")}
-
-
+
+ <>
{activeApplicationRounds.length > 0 ? (
-
-
- {t("recurringLander:roundHeadings.active")}
-
+
+ {t("recurringLander:roundHeadings.active")}
{activeApplicationRounds.map((applicationRound) => (
))}
-
+
) : (
-
-
- {t("recurringLander:roundHeadings.active")}
-
+
+ {t("recurringLander:roundHeadings.active")}
{t("recurringLander:noRounds")}
-
+
)}
{pendingApplicationRounds.length > 0 && (
-
-
- {t("recurringLander:roundHeadings.pending")}
-
+
+ {t("recurringLander:roundHeadings.pending")}
{pendingApplicationRounds.map((applicationRound) => (
))}
-
+
)}
{pastApplicationRounds.length > 0 && (
-
-
- {t("recurringLander:roundHeadings.past")}
-
+
+ {t("recurringLander:roundHeadings.past")}
{pastApplicationRounds.map((applicationRound) => (
))}
-
+
)}
-
+ >
>
);
-};
+}
export default RecurringLander;
diff --git a/apps/ui/pages/reservation-unit/[...params].tsx b/apps/ui/pages/reservation-unit/[...params].tsx
index a9159c584c..38e61d4fc2 100644
--- a/apps/ui/pages/reservation-unit/[...params].tsx
+++ b/apps/ui/pages/reservation-unit/[...params].tsx
@@ -8,7 +8,7 @@ import { FormProvider, useForm } from "react-hook-form";
import type { GetServerSidePropsContext } from "next";
import { useTranslation } from "next-i18next";
import { breakpoints } from "common/src/common/style";
-import { fontRegular, H2 } from "common/src/common/typography";
+import { H1, H4 } from "common/src/common/typography";
import {
CustomerTypeChoice,
useConfirmReservationMutation,
@@ -24,8 +24,6 @@ import {
ReservationStateChoice,
} from "@gql/gql-types";
import { type Inputs } from "common/src/reservation-form/types";
-import { Subheading } from "common/src/reservation-form/styles";
-import { Container } from "common";
import { createApolloClient } from "@/modules/apolloClient";
import { getReservationPath, getReservationUnitPath } from "@/modules/urls";
import Sanitize from "@/components/common/Sanitize";
@@ -36,11 +34,9 @@ import {
} 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";
+import { Step0 } from "@/components/reservation/Step0";
+import { Step1 } from "@/components/reservation/Step1";
import { ReservationStep } from "@/modules/types";
-import { JustForDesktop } from "@/modules/style/layout";
-import { PinkBox } from "@/components/reservation-unit/ReservationUnitStyles";
import { getCommonServerSideProps } from "@/modules/serverUtils";
import { useConfirmNavigation } from "@/hooks/useConfirmNavigation";
import { base64encode, filterNonNullable } from "common/src/helpers";
@@ -53,54 +49,45 @@ import {
getTranslationSafe,
} from "common/src/common/util";
import { ApolloError } from "@apollo/client";
-
-const StyledContainer = styled(Container)`
- padding-top: var(--spacing-m);
-
- @media (min-width: ${breakpoints.m}) {
- margin-bottom: var(--spacing-layout-l);
+import { PinkBox as PinkBoxBase } from "@/components/reservation/styles";
+import { Flex } from "common/styles/util";
+import BreadcrumbWrapper from "@/components/common/BreadcrumbWrapper";
+import { ReservationPageWrapper } from "@/components/reservations/styles";
+
+const StyledReservationInfoCard = styled(ReservationInfoCard)`
+ grid-column: 1 / -1;
+ grid-row: 2;
+ @media (width > ${breakpoints.m}) {
+ grid-column: span 1 / -1;
+ grid-row: 1 / span 2;
}
`;
-const Columns = styled.div`
- grid-template-columns: 1fr;
- display: grid;
- align-items: flex-start;
- gap: var(--spacing-m);
-
+const PinkBox = styled(PinkBoxBase)`
+ grid-column: 1 / -1;
+ grid-row: 4;
@media (min-width: ${breakpoints.m}) {
- & > div:nth-of-type(1) {
- order: 2;
- }
-
- margin-top: var(--spacing-xl);
- grid-template-columns: 1fr 378px;
+ grid-column: span 1 / -1;
+ grid-row: 3;
}
`;
-const Title = styled(H2).attrs({ as: "h1" })`
- display: flex;
- align-items: center;
- gap: var(--spacing-xs);
- margin-top: 0;
-
- svg {
- color: var(--color-tram);
+const StyledForm = styled.form`
+ grid-column: 1 / -1;
+ grid-row: 3;
+ @media (width > ${breakpoints.m}) {
+ grid-column: span 1;
+ grid-row: 2 / -1;
}
`;
-const BodyContainer = styled.div`
- ${fontRegular}
-
- a {
- color: var(--color-bus);
+const TitleSection = styled(Flex)`
+ grid-column: 1 / -1;
+ @media (min-width: ${breakpoints.m}) {
+ grid-column: span 1;
}
`;
-const StyledStepper = styled(Stepper)<{ small: boolean }>`
- ${({ small }) => !small && "max-width: 300px;"}
-`;
-
/// 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.
@@ -123,7 +110,7 @@ const useRemoveStoredReservation = () => {
// - using back multiple times breaks the confirmation hook (bypassing it or blocking the navigation while deleting the reservation)
// - requires complex logic to handle the steps and keep the url in sync with what's on the page
// - forward / backward navigation work differently
-function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null {
+function NewReservation(props: PropsNarrowed): JSX.Element | null {
const { t, i18n } = useTranslation();
const router = useRouter();
@@ -203,6 +190,7 @@ function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null {
const confirmMessage = t("reservations:confirmNavigation");
// NOTE this is the only place where reservation is deleted, don't add a second place or it gets called repeatedly
const onNavigationConfirmed = useCallback(() => {
+ // TODO rewrite browser history so user will not end up here if they press next
deleteReservation({
variables: {
input: {
@@ -366,95 +354,118 @@ function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null {
}, [step, generalFields, reservation, reservationUnit]);
const lang = convertLanguageCode(i18n.language);
- const termsOfUseContent = getTranslationSafe(
- reservationUnit,
- "termsOfUse",
- lang
- );
+ const termsOfUse = getTranslationSafe(reservationUnit, "termsOfUse", lang);
const infoReservation = {
...reservation,
reservationUnit: reservationUnit != null ? [reservationUnit] : [],
};
+ // TODO rework so we submit the form values here
+ const onSubmit = (values: unknown) => {
+ if (step === 0) {
+ onSubmitStep0(values);
+ }
+ if (step === 1) {
+ onSubmitStep1();
+ }
+ };
+
return (
-
-
-
-
- {termsOfUseContent && (
-
-
-
- {t("reservations:reservationInfoBoxHeading")}
-
-
-
-
+
+
+
+ {termsOfUse && (
+
+ {t("reservations:reservationInfoBoxHeading")}
+
+
+ )}
+
+ {pageTitle}
+ {/* TODO what's the logic here?
+ * in what case are there more than 2 steps?
+ * why do we not show that?
+ * TODO why isn't this shown when creating a paid version? I think there was on purpose reason for that? maybe?
+ */}
+ {steps.length <= 2 && (
+ {
+ const target = e.currentTarget;
+ const s = target
+ .getAttribute("data-testid")
+ ?.replace("hds-stepper-step-", "");
+ if (s) {
+ setStep(parseInt(s, 10));
+ }
+ }}
+ steps={steps}
+ />
+ )}
+
+
+ {step === 0 && (
+
)}
-
-
-
-
-
{pageTitle}
- {/* TODO what's the logic here?
- * in what case are there more than 2 steps?
- * why do we not show that?
- * TODO why isn't this shown when creating a paid version? I think there was on purpose reason for that? maybe?
- */}
- {steps.length <= 2 && (
- {
- const target = e.currentTarget;
- const s = target
- .getAttribute("data-testid")
- ?.replace("hds-stepper-step-", "");
- if (s) {
- setStep(parseInt(s, 10));
- }
- }}
- steps={steps}
- />
- )}
-
- {step === 0 && (
-
- )}
- {step === 1 && (
- 2}
- setStep={setStep}
- />
- )}
-
-
-
-
+ {step === 1 && (
+ 2}
+ setStep={setStep}
+ />
+ )}
+
+
+
+ );
+}
+
+function NewReservationWrapper(props: PropsNarrowed): JSX.Element | null {
+ const { t } = useTranslation();
+ const { reservation, reservationUnit } = props;
+ const lang = convertLanguageCode("fi");
+ const routes = [
+ {
+ slug: "/search",
+ title: t("breadcrumb:search"),
+ },
+ {
+ slug: getReservationUnitPath(reservationUnit?.pk),
+ title: getTranslationSafe(reservationUnit, "name", lang) ?? "",
+ },
+ {
+ // NOTE Don't set slug. It hides the mobile breadcrumb
+ title: t("reservations:reservationName", { id: reservation.pk }),
+ },
+ ];
+
+ return (
+ <>
+
+
+ >
);
}
+export default NewReservationWrapper;
+
type Props = Awaited>["props"];
type PropsNarrowed = Exclude;
@@ -530,5 +541,3 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) {
notFound: true,
};
}
-
-export default ReservationUnitReservation;
diff --git a/apps/ui/pages/reservation-unit/[id].tsx b/apps/ui/pages/reservation-unit/[id].tsx
index 93388a7288..253c267416 100644
--- a/apps/ui/pages/reservation-unit/[id].tsx
+++ b/apps/ui/pages/reservation-unit/[id].tsx
@@ -6,12 +6,14 @@ import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import styled from "styled-components";
import { addYears } from "date-fns";
import {
+ convertLanguageCode,
fromUIDate,
+ getTranslationSafe,
isValidDate,
toApiDate,
toUIDate,
} from "common/src/common/util";
-import { formatters as getFormatters } from "common";
+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";
@@ -33,7 +35,7 @@ import {
isPriceFree,
toNumber,
} from "common/src/helpers";
-import Head from "@/components/reservation-unit/Head";
+import { Head } from "@/components/reservation-unit/Head";
import { AddressSection } from "@/components/reservation-unit/Address";
import Sanitize from "@/components/common/Sanitize";
import {
@@ -56,7 +58,7 @@ import {
isReservationUnitPublished,
isReservationUnitReservable,
} from "@/modules/reservationUnit";
-import EquipmentList from "@/components/reservation-unit/EquipmentList";
+import { EquipmentList } from "@/components/reservation-unit/EquipmentList";
import { JustForDesktop, JustForMobile } from "@/modules/style/layout";
import {
type FocusTimeSlot,
@@ -72,19 +74,8 @@ import {
} from "@/modules/reservable";
import SubventionSuffix from "@/components/reservation/SubventionSuffix";
import InfoDialog from "@/components/common/InfoDialog";
-import {
- BottomContainer,
- BottomWrapper,
- CalendarWrapper,
- Content,
- MapWrapper,
- PaddedContent,
- StyledNotification,
- Subheading,
- Wrapper,
-} from "@/components/reservation-unit/ReservationUnitStyles";
import { QuickReservation } from "@/components/reservation-unit/QuickReservation";
-import ReservationInfoContainer from "@/components/reservation-unit/ReservationInfoContainer";
+import { ReservationInfoContainer } from "@/components/reservation-unit/ReservationInfoContainer";
import {
getCommonServerSideProps,
getGenericTerms,
@@ -96,15 +87,21 @@ import {
PendingReservationFormSchema,
type PendingReservationFormType,
} from "@/components/reservation-unit/schema";
-import { MediumButton } from "@/styles/util";
import LoginFragment from "@/components/LoginFragment";
import { RELATED_RESERVATION_STATES } from "common/src/const";
import { useReservableTimes } from "@/hooks/useReservableTimes";
import { errorToast } from "common/src/common/toast";
import { ReservationTimePicker } from "@/components/reservation/ReservationTimePicker";
import { ApolloError } from "@apollo/client";
-import { getReservationInProgressPath } from "@/modules/urls";
-import { ReservationPageWrapper } from "@/components/reservations/styles";
+import { ReservationUnitPageWrapper } from "@/components/reservations/styles";
+import {
+ getReservationInProgressPath,
+ getSingleSearchPath,
+} from "@/modules/urls";
+import { Notification } from "hds-react";
+import BreadcrumbWrapper from "@/components/common/BreadcrumbWrapper";
+import { Flex } from "common/styles/util";
+import { SubmitButton } from "@/styles/util";
type Props = Awaited>["props"];
type PropsNarrowed = Exclude;
@@ -249,20 +246,6 @@ const StyledApplicationRoundScheduleDay = styled.div`
}
`;
-const SubmitButton = styled(MediumButton)`
- white-space: nowrap;
-
- > span {
- margin: 0 !important;
- padding-right: var(--spacing-3-xs);
- padding-left: var(--spacing-3-xs);
- }
-
- @media (min-width: ${breakpoints.m}) {
- order: unset;
- }
-`;
-
// Returns an element for a weekday in the application round timetable, with up to two timespans
function ApplicationRoundScheduleDay(
props: Omit
@@ -328,15 +311,14 @@ function SubmitFragment(
);
}
-const QuickReservationWrapper = styled.div`
- grid-column-end: -1;
-`;
-
-const PageContentWrapper = styled.div`
+const PageContentWrapper = styled(Flex).attrs({
+ $gap: "s",
+})`
grid-column: 1 / -2;
- @media (min-width: ${breakpoints.l}) {
- grid-row: 1;
+ @media (width > ${breakpoints.l}) {
+ grid-row: 2 / -1;
+ grid-column: 1;
}
`;
@@ -390,7 +372,7 @@ function ReservationUnit({
resolver: zodResolver(PendingReservationFormSchema),
});
- const { watch, setValue } = reservationForm;
+ const { watch } = reservationForm;
const durationValue = watch("duration");
const dateValue = watch("date");
@@ -503,13 +485,6 @@ function ReservationUnit({
}
};
- // Set default duration if it's not set
- useEffect(() => {
- if (!durationValue) {
- setValue("duration", durationOptions[0]?.value);
- }
- }, [dateValue, timeValue, durationValue, durationOptions, setValue]);
-
// store reservation unit overall reservability to use in JSX and pass to some child elements
const [reservationUnitIsReservable, reason] =
isReservationUnitReservable(reservationUnit);
@@ -634,7 +609,7 @@ function ReservationUnit({
!isReservationStartInFuture(reservationUnit) && reservationUnitIsReservable;
return (
-
+
-
-
- {isUnitReservable && (
-
+ {isUnitReservable && (
+
+ ) : undefined
+ }
+ />
+ )}
+
+
+
+