From d1fcf06ace04da497251c9962c46c4bb4ead4cc3 Mon Sep 17 00:00:00 2001 From: Joonatan Kuosa Date: Fri, 4 Oct 2024 08:53:50 +0300 Subject: [PATCH 01/16] fix: missing gap on seasonal search page --- apps/ui/components/common/ListWithPagination.tsx | 3 +++ apps/ui/pages/search/single.tsx | 13 ++----------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/ui/components/common/ListWithPagination.tsx b/apps/ui/components/common/ListWithPagination.tsx index dc2b4a02de..7d7148dc60 100644 --- a/apps/ui/components/common/ListWithPagination.tsx +++ b/apps/ui/components/common/ListWithPagination.tsx @@ -27,6 +27,9 @@ const NoResults = styled.div` const ListContainer = styled.div` margin-top: var(--spacing-layout-s); + display: flex; + flex-flow: column nowrap; + gap: var(--spacing-m); `; const Paginator = styled.div` diff --git a/apps/ui/pages/search/single.tsx b/apps/ui/pages/search/single.tsx index 4b41414e55..e77ad068ce 100644 --- a/apps/ui/pages/search/single.tsx +++ b/apps/ui/pages/search/single.tsx @@ -44,15 +44,6 @@ const HeadContainer = styled.div` const Heading = styled(H2).attrs({ as: "h1" })``; -const BottomWrapper = styled(Container)` - padding-top: var(--spacing-l); - [class*="ListContainer"] { - display: flex; - flex-flow: column nowrap; - gap: var(--spacing-m); - } -`; - export async function getServerSideProps(ctx: GetServerSidePropsContext) { const { locale, query } = ctx; const commonProps = getCommonServerSideProps(); @@ -150,7 +141,7 @@ function SearchSingle({
- + ( @@ -161,7 +152,7 @@ function SearchSingle({ fetchMore={(cursor) => fetchMore(cursor)} sortingComponent={} /> - +
); From 76cec1d3ade3c6220e7db12b0cbec7dfde186400 Mon Sep 17 00:00:00 2001 From: Joonatan Kuosa Date: Fri, 4 Oct 2024 12:32:04 +0300 Subject: [PATCH 02/16] fix: button styling - All secondary buttons / link buttons use white background. - Remove overrides from buttons inside a Card. - Replace !important overrides with focus / hover vars. --- .../components/search/ReservationUnitCard.tsx | 20 ++++++--------- apps/ui/styles/global.scss | 25 +++++++++++-------- packages/common/src/components/Card.tsx | 8 ------ packages/common/styles/buttonCss.ts | 10 +++----- 4 files changed, 26 insertions(+), 37 deletions(-) diff --git a/apps/ui/components/search/ReservationUnitCard.tsx b/apps/ui/components/search/ReservationUnitCard.tsx index 3dddd4c4a0..5fc35b8d97 100644 --- a/apps/ui/components/search/ReservationUnitCard.tsx +++ b/apps/ui/components/search/ReservationUnitCard.tsx @@ -8,8 +8,6 @@ import { import React from "react"; import { useTranslation } from "next-i18next"; import NextImage from "next/image"; -import styled from "styled-components"; -import { fontMedium } from "common/src/common/typography"; import type { ReservationUnitCardFieldsFragment } from "@gql/gql-types"; import { getMainImage, getTranslation } from "@/modules/util"; import { reservationUnitPrefix } from "@/modules/const"; @@ -25,13 +23,6 @@ interface IProps { removeReservationUnit: (reservationUnit: Node) => void; } -/* TODO something is overriding button font-family to be bold */ -const StyledButton = styled(Button).attrs({ size: "small" })` - && { - ${fontMedium} - } -`; - export function ReservationUnitCard({ reservationUnit, selectReservationUnit, @@ -91,18 +82,21 @@ export function ReservationUnitCard({ const buttons = []; if (containsReservationUnit(reservationUnit)) { buttons.push( - } onClick={() => removeReservationUnit(reservationUnit)} data-testid="reservation-unit-card__button--select" key={t("common:removeReservationUnit")} > {t("common:removeReservationUnit")} - + ); } else { buttons.push( - } onClick={() => selectReservationUnit(reservationUnit)} @@ -110,7 +104,7 @@ export function ReservationUnitCard({ key={t("common:selectReservationUnit")} > {t("common:selectReservationUnit")} - + ); } buttons.push( diff --git a/apps/ui/styles/global.scss b/apps/ui/styles/global.scss index 1f024240f0..97f2d1445a 100644 --- a/apps/ui/styles/global.scss +++ b/apps/ui/styles/global.scss @@ -96,16 +96,21 @@ button { } [class*="button_hds-button--secondary"]:not(:disabled) { - font-family: var(--font-bold); - --color: var(--color-black) !important; - color: var(--color) !important; - border-color: var(--color-black) !important; - --background-color: var(--color-white) !important; - --background-color-hover: var(--color-black-20) !important; - --border-color: var(--color-black) !important; - --border-color-hover: var(--border-color) !important; - --border-color-focus: var(--border-color) !important; - --border-color-hover-focus: var(--border-color) !important; + font-family: var(--font-medium); + --color: var(--color-black); + color: var(--color); + border-color: var(--color-black); + --color-hover: var(--color-black); + --color-focus: var(--color-black); + --color-hover-focus: var(--color-black); + --background-color: var(--color-white); + --background-color-hover: var(--color-black-20); + --background-color-focus: var(--color-black-20); + --background-color-hover-focus: var(--color-black-20); + --border-color: var(--color-black); + --border-color-hover: var(--border-color); + --border-color-focus: var(--border-color); + --border-color-hover-focus: var(--border-color); } // cookiehub overrides diff --git a/packages/common/src/components/Card.tsx b/packages/common/src/components/Card.tsx index dec2549070..89fbc6a024 100644 --- a/packages/common/src/components/Card.tsx +++ b/packages/common/src/components/Card.tsx @@ -308,14 +308,6 @@ const ButtonContainer = styled.div` gap: var(--spacing-xs); flex-direction: column; align-items: flex-end; - > * { - --background-color: var(--color-white) !important; - --background-color-disabled: var(--color-white) !important; - --border-color: var(--color-black) !important; - color: var(--color-black); - background: var(--color-white); - width: 100%; - } @media (min-width: ${breakpoints.m}) { .card--default & { diff --git a/packages/common/styles/buttonCss.ts b/packages/common/styles/buttonCss.ts index 38d0c3f830..82921a8e8b 100644 --- a/packages/common/styles/buttonCss.ts +++ b/packages/common/styles/buttonCss.ts @@ -9,13 +9,11 @@ export type ButtonStyleProps = { export const ButtonCss = css` --background-color-hover: ${({ variant }) => - variant === "primary" ? "var(--color-bus-dark)" : "var(--color-black-5)"}; + variant === "primary" ? "var(--color-bus-dark)" : "var(--color-black-20)"}; --color-hover: ${({ variant }) => variant === "primary" ? "var(--color-white)" : "var(--color-black)"}; - --background-color-focus: ${({ variant }) => - variant === "primary" ? "var(--color-bus)" : "transparent"}; - --color-focus: ${({ variant }) => - variant === "primary" ? "var(--color-white)" : "var(--color-black)"}; + --background-color-focus: var(--background-color-hover); + --color-focus: var(--color-hover); --focus-outline-color: var(--color-focus-outline); --outline-gutter: 2px; --outline-width: 3px; @@ -27,7 +25,7 @@ export const ButtonCss = css` opacity: ${({ disabled }) => (disabled ? "0.5" : "1")}; text-decoration: none; background-color: ${({ variant }) => - variant === "primary" ? "var(--color-bus)" : "transparent"}; + variant === "primary" ? "var(--color-bus)" : "var(--color-white)"}; color: ${({ variant }) => variant === "primary" ? "var(--color-white)" : "var(--color-black)"}; padding: ${({ size }) => (size === "large" ? "12px 20px" : "0 20px")}; From 78eb29b703325a587aba6ba5fa93212513856d5f Mon Sep 17 00:00:00 2001 From: Joonatan Kuosa Date: Thu, 3 Oct 2024 09:18:02 +0300 Subject: [PATCH 03/16] refactor: add url builders to customer ui --- .../applications/ApplicationCard.tsx | 7 +- apps/ui/components/common/Navigation.tsx | 6 +- apps/ui/components/index/Purposes.tsx | 4 +- .../index/ReservationUnitSearch.tsx | 6 +- apps/ui/components/index/Units.tsx | 4 +- .../recurring/ApplicationRoundCard.tsx | 30 ++----- apps/ui/components/reservation-unit/Head.tsx | 14 +-- .../reservation-unit/RelatedUnits.tsx | 4 +- .../reservation-unit/ReservationUnitModal.tsx | 5 +- .../reservation/DeleteCancelled.tsx | 4 +- apps/ui/components/reservation/EditStep1.tsx | 4 +- .../reservation/ReservationCard.tsx | 10 +-- .../reservation/ReservationConfirmation.tsx | 12 +-- .../reservation/ReservationFail.tsx | 4 +- .../reservation/ReservationInfoCard.tsx | 14 +-- .../UnpaidReservationNotification.tsx | 9 +- .../components/search/ReservationUnitCard.tsx | 19 ++--- .../SingleSearchReservationUnitCard.tsx | 5 +- apps/ui/modules/const.ts | 10 --- apps/ui/modules/urls.ts | 85 +++++++++++++++++++ apps/ui/modules/util.ts | 41 +-------- apps/ui/pages/application/[id]/sent.tsx | 8 +- apps/ui/pages/application/[id]/view.tsx | 8 +- apps/ui/pages/recurring/index.tsx | 2 +- .../ui/pages/reservation-unit/[...params].tsx | 13 ++- apps/ui/pages/reservations/[id]/cancel.tsx | 4 +- apps/ui/pages/reservations/[id]/edit.tsx | 4 +- apps/ui/pages/reservations/[id]/index.tsx | 15 ++-- 28 files changed, 177 insertions(+), 174 deletions(-) create mode 100644 apps/ui/modules/urls.ts diff --git a/apps/ui/components/applications/ApplicationCard.tsx b/apps/ui/components/applications/ApplicationCard.tsx index 7f83521096..abbc505451 100644 --- a/apps/ui/components/applications/ApplicationCard.tsx +++ b/apps/ui/components/applications/ApplicationCard.tsx @@ -26,6 +26,7 @@ import { ConfirmationDialog } from "common/src/components/ConfirmationDialog"; import Card from "common/src/components/Card"; import StatusLabel from "common/src/components/StatusLabel"; import { type StatusLabelType } from "common/src/tags"; +import { getApplicationPath } from "@/modules/urls"; const StyledButton = styled(Button).attrs({ variant: "secondary", @@ -174,11 +175,7 @@ function ApplicationCard({ application, actionCallback }: Props): JSX.Element { , {t("applicationCard:edit")} diff --git a/apps/ui/components/common/Navigation.tsx b/apps/ui/components/common/Navigation.tsx index 2c8d8e410e..95ca8484aa 100644 --- a/apps/ui/components/common/Navigation.tsx +++ b/apps/ui/components/common/Navigation.tsx @@ -21,11 +21,11 @@ import { getLocalizationLang } from "common/src/helpers"; import { env } from "@/env.mjs"; import { applicationsPrefix, - recurringReservationsPrefix, reservationsPrefix, reservationUnitPrefix, + seasonalPrefix, singleSearchPrefix, -} from "@/modules/const"; +} from "@/modules/urls"; type HeaderProps = { apiBaseUrl: string; @@ -116,7 +116,7 @@ const menuItems = [ }, { label: "navigation:Item.spaceReservation", - routes: [recurringReservationsPrefix], + routes: [seasonalPrefix], }, { label: "navigation:Item.reservations", diff --git a/apps/ui/components/index/Purposes.tsx b/apps/ui/components/index/Purposes.tsx index 989528cefd..03c8dd5bfe 100644 --- a/apps/ui/components/index/Purposes.tsx +++ b/apps/ui/components/index/Purposes.tsx @@ -8,8 +8,8 @@ import { breakpoints } from "common/src/common/style"; import { H3 } from "common/src/common/typography"; import type { PurposeNode } from "@gql/gql-types"; import { ShowAllContainer } from "common/src/components"; -import { singleSearchPrefix } from "../../modules/const"; -import { getTranslation } from "../../modules/util"; +import { singleSearchPrefix } from "@/modules/urls"; +import { getTranslation } from "@/modules/util"; import ReservationUnitSearch from "./ReservationUnitSearch"; import { anchorStyles, focusStyles } from "common/styles/cssFragments"; import { pixel } from "@/styles/util"; diff --git a/apps/ui/components/index/ReservationUnitSearch.tsx b/apps/ui/components/index/ReservationUnitSearch.tsx index 74304a19ab..3ea8886bb9 100644 --- a/apps/ui/components/index/ReservationUnitSearch.tsx +++ b/apps/ui/components/index/ReservationUnitSearch.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; import styled from "styled-components"; import { breakpoints } from "common/src/common/style"; -import { singleSearchPrefix } from "@/modules/const"; +import { getSingleSearchPath } from "@/modules/urls"; const StyledTextInput = styled(TextInput)` position: relative; @@ -37,7 +37,9 @@ const ReservationUnitSearch = (): JSX.Element => { const handleSubmit = (event: React.FormEvent | React.MouseEvent) => { event.preventDefault(); - router.push(`${singleSearchPrefix}?textSearch=${searchTerm}`); + const params = new URLSearchParams(); + params.set("textSearch", searchTerm); + router.push(getSingleSearchPath(params)); }; return ( diff --git a/apps/ui/components/index/Units.tsx b/apps/ui/components/index/Units.tsx index 8453da9bc4..1d48f32569 100644 --- a/apps/ui/components/index/Units.tsx +++ b/apps/ui/components/index/Units.tsx @@ -7,8 +7,8 @@ import Link from "next/link"; import { fontMedium, H3 } from "common/src/common/typography"; import type { UnitNode } from "@gql/gql-types"; import { IconButton } from "common/src/components"; -import { singleSearchPrefix } from "../../modules/const"; -import { getTranslation } from "../../modules/util"; +import { singleSearchPrefix } from "@/modules/urls"; +import { getTranslation } from "@/modules/util"; import { anchorStyles, focusStyles } from "common/styles/cssFragments"; type Props = { diff --git a/apps/ui/components/recurring/ApplicationRoundCard.tsx b/apps/ui/components/recurring/ApplicationRoundCard.tsx index 1643ddc0e1..2da89a5402 100644 --- a/apps/ui/components/recurring/ApplicationRoundCard.tsx +++ b/apps/ui/components/recurring/ApplicationRoundCard.tsx @@ -1,17 +1,16 @@ import React from "react"; import { IconArrowRight, IconLinkExternal } from "hds-react"; -import { TFunction, useTranslation } from "next-i18next"; -import { useRouter } from "next/router"; -import ClientOnly from "common/src/ClientOnly"; +import { type TFunction, useTranslation } from "next-i18next"; import { type ApplicationRoundFieldsFragment, ApplicationRoundStatusChoice, } from "@gql/gql-types"; -import { formatDateTime, searchUrl } from "@/modules/util"; +import { formatDateTime } from "@/modules/util"; import { getApplicationRoundName } from "@/modules/applicationRound"; import { isValid } from "date-fns"; import Card from "common/src/components/Card"; import { ButtonLikeLink } from "@/components/common/ButtonLikeLink"; +import { getApplicationRoundPath, getSeasonalSearchPath } from "@/modules/urls"; interface Props { applicationRound: ApplicationRoundFieldsFragment; @@ -44,11 +43,9 @@ function translateRoundDate( } } -const ApplicationRoundCard = ({ applicationRound }: Props): JSX.Element => { +export function ApplicationRoundCard({ applicationRound }: Props): JSX.Element { const { t } = useTranslation(); - const history = useRouter(); - const state = applicationRound.status; if (state == null) { // eslint-disable-next-line no-console @@ -71,7 +68,7 @@ const ApplicationRoundCard = ({ applicationRound }: Props): JSX.Element => { const buttons = [ @@ -83,13 +80,7 @@ const ApplicationRoundCard = ({ applicationRound }: Props): JSX.Element => { buttons.push( { - e.preventDefault(); - if (applicationRound.pk) { - history.push(searchUrl({ applicationRound: applicationRound.pk })); - } - }} + href={getSeasonalSearchPath(applicationRound.pk)} > {t("application:Intro.startNewApplication")} @@ -102,11 +93,4 @@ const ApplicationRoundCard = ({ applicationRound }: Props): JSX.Element => { {reservationPeriod} ); -}; - -// Hack to deal with hydration errors -export default ({ applicationRound }: Props): JSX.Element => ( - - - -); +} diff --git a/apps/ui/components/reservation-unit/Head.tsx b/apps/ui/components/reservation-unit/Head.tsx index 153e459994..29ed616404 100644 --- a/apps/ui/components/reservation-unit/Head.tsx +++ b/apps/ui/components/reservation-unit/Head.tsx @@ -8,12 +8,7 @@ import { fontRegular, H2, H3 } from "common/src/common/typography"; import { breakpoints } from "common/src/common/style"; import { ReservationKind, type ReservationUnitPageQuery } from "@gql/gql-types"; import { Container } from "common"; -import { - formatDate, - getTranslation, - orderImages, - singleSearchUrl, -} from "@/modules/util"; +import { formatDate, getTranslation, orderImages } from "@/modules/util"; import IconWithText from "../common/IconWithText"; import { Images } from "./Images"; import { @@ -26,6 +21,7 @@ import { import BreadcrumbWrapper from "../common/BreadcrumbWrapper"; import { isReservationStartInFuture } from "@/modules/reservation"; import { filterNonNullable } from "common/src/helpers"; +import { getSingleSearchPath } from "@/modules/urls"; type QueryT = NonNullable; interface PropsType { @@ -147,8 +143,6 @@ function Head({ }: PropsType): JSX.Element { const { t } = useTranslation(); - const searchUrl = singleSearchUrl(); - const minDur = reservationUnit.minReservationDuration ?? 0; const maxDur = reservationUnit.maxReservationDuration ?? 0; const minReservationDuration = formatDuration(minDur / 60, t, true); @@ -156,13 +150,11 @@ function Head({ const pricing = getActivePricing(reservationUnit); const unitPrice = pricing ? getPriceString({ t, pricing }) : undefined; - const isPaid = isReservationUnitPaid(reservationUnit.pricings); const hasSubventionSuffix = pricing && isPaid && subventionSuffix != null; - const reservationUnitName = getReservationUnitName(reservationUnit); - const unitName = getUnitName(reservationUnit.unit ?? undefined); + const searchUrl = getSingleSearchPath(); const iconsTexts = filterNonNullable([ reservationUnit.reservationUnitType != null diff --git a/apps/ui/components/reservation-unit/RelatedUnits.tsx b/apps/ui/components/reservation-unit/RelatedUnits.tsx index ef37740703..5e0fc712f7 100644 --- a/apps/ui/components/reservation-unit/RelatedUnits.tsx +++ b/apps/ui/components/reservation-unit/RelatedUnits.tsx @@ -6,7 +6,6 @@ import styled from "styled-components"; import { useMedia } from "react-use"; import { breakpoints } from "common/src/common/style"; import type { RelatedReservationUnitsQuery } from "@gql/gql-types"; -import { reservationUnitPath } from "@/modules/const"; import { getMainImage } from "@/modules/util"; import Carousel from "../Carousel"; import { @@ -22,6 +21,7 @@ import { convertLanguageCode, getTranslationSafe, } from "common/src/common/util"; +import { getReservationUnitPath } from "@/modules/urls"; type RelatedQueryT = NonNullable< RelatedReservationUnitsQuery["reservationUnits"] @@ -131,7 +131,7 @@ function RelatedUnitCard({ } const buttons = [ {t("reservationUnitCard:seeMore")} diff --git a/apps/ui/components/reservation-unit/ReservationUnitModal.tsx b/apps/ui/components/reservation-unit/ReservationUnitModal.tsx index a4af29a1d6..da9ea409ad 100644 --- a/apps/ui/components/reservation-unit/ReservationUnitModal.tsx +++ b/apps/ui/components/reservation-unit/ReservationUnitModal.tsx @@ -22,12 +22,12 @@ import { type ApplicationQuery, } from "@gql/gql-types"; import { filterNonNullable, getImageSource } from "common/src/helpers"; -import { reservationUnitPath } from "@/modules/const"; import { getAddressAlt, getMainImage, getTranslation } from "@/modules/util"; import { MediumButton } from "@/styles/util"; import { getApplicationRoundName } from "@/modules/applicationRound"; import { getReservationUnitName, getUnitName } from "@/modules/reservationUnit"; import IconWithText from "../common/IconWithText"; +import { getReservationUnitPath } from "@/modules/urls"; const Container = styled.div` width: 100%; @@ -161,8 +161,7 @@ function ReservationUnitCard({ {name} {unitName} diff --git a/apps/ui/components/reservation/DeleteCancelled.tsx b/apps/ui/components/reservation/DeleteCancelled.tsx index 1304e6c87a..79f4e72a7d 100644 --- a/apps/ui/components/reservation/DeleteCancelled.tsx +++ b/apps/ui/components/reservation/DeleteCancelled.tsx @@ -9,7 +9,7 @@ import { Container } from "common"; import { signOut } from "common/src/browserHelpers"; import { Paragraph } from "./styles"; import { LinkButton } from "../../styles/util"; -import { singleSearchUrl } from "../../modules/util"; +import { getSingleSearchPath } from "@/modules/urls"; type Props = { reservationPk: string; @@ -89,7 +89,7 @@ const DeleteCancelled = ({ reservationPk, error, apiBaseUrl }: Props) => { marginTop: "var(--spacing-3-xl)", }} > - + {t("reservations:backToSearch")} diff --git a/apps/ui/components/reservation/EditStep1.tsx b/apps/ui/components/reservation/EditStep1.tsx index a0a04e7552..184d523404 100644 --- a/apps/ui/components/reservation/EditStep1.tsx +++ b/apps/ui/components/reservation/EditStep1.tsx @@ -7,7 +7,6 @@ import { breakpoints } from "common/src/common/style"; import React, { useMemo, useState } from "react"; import { useTranslation } from "next-i18next"; import styled from "styled-components"; -import { reservationsPrefix } from "@/modules/const"; import { filterNonNullable } from "common/src/helpers"; import { errorToast } from "common/src/common/toast"; import { @@ -21,6 +20,7 @@ import { PendingReservationFormType } from "../reservation-unit/schema"; import { type UseFormReturn } from "react-hook-form"; import { convertReservationFormToApi } from "@/modules/reservation"; import { AcceptTerms } from "./AcceptTerms"; +import { getReservationPath } from "@/modules/urls"; type ReservationUnitNodeT = NonNullable< ReservationUnitPageQuery["reservationUnit"] @@ -174,7 +174,7 @@ export function EditStep1({ {t("common:prev")} diff --git a/apps/ui/components/reservation/ReservationCard.tsx b/apps/ui/components/reservation/ReservationCard.tsx index 7dd28f3793..97a6822724 100644 --- a/apps/ui/components/reservation/ReservationCard.tsx +++ b/apps/ui/components/reservation/ReservationCard.tsx @@ -8,12 +8,7 @@ import { type ListReservationsQuery, ReservationStateChoice, } from "@gql/gql-types"; -import { - capitalize, - formatDateTimeRange, - getMainImage, - reservationsUrl, -} from "@/modules/util"; +import { capitalize, formatDateTimeRange, getMainImage } from "@/modules/util"; import { isReservationCancellable, getNormalizedReservationOrderStatus, @@ -28,6 +23,7 @@ import { ReservationStatus } from "./ReservationStatus"; import { ButtonLikeLink } from "../common/ButtonLikeLink"; import { getImageSource } from "common/src/helpers"; import Card from "common/src/components/Card"; +import { getReservationPath } from "@/modules/urls"; type CardType = "upcoming" | "past" | "cancelled"; @@ -109,7 +105,7 @@ function ReservationCard({ reservation, type }: PropsT): JSX.Element { if (type === "upcoming" && isReservationCancellable(reservation)) { buttons.push( diff --git a/apps/ui/components/reservation/ReservationConfirmation.tsx b/apps/ui/components/reservation/ReservationConfirmation.tsx index e695e81c3d..d04b751cce 100644 --- a/apps/ui/components/reservation/ReservationConfirmation.tsx +++ b/apps/ui/components/reservation/ReservationConfirmation.tsx @@ -18,12 +18,12 @@ import { Subheading } from "common/src/reservation-form/styles"; import { breakpoints } from "common/src/common/style"; import { IconButton } from "common/src/components"; import { signOut } from "common/src/browserHelpers"; -import { getReservationUnitInstructionsKey } from "../../modules/reservationUnit"; -import { getTranslation, reservationsUrl } from "../../modules/util"; -import { BlackButton } from "../../styles/util"; +import { getReservationUnitInstructionsKey } from "@/modules/reservationUnit"; +import { getTranslation } from "@/modules/util"; +import { BlackButton } from "@/styles/util"; import { Paragraph } from "./styles"; -import { reservationUnitPath } from "../../modules/const"; import { ButtonLikeLink } from "../common/ButtonLikeLink"; +import { getReservationUnitPath, reservationsPath } from "@/modules/urls"; type Node = NonNullable; type Props = { @@ -129,7 +129,7 @@ function ReservationConfirmation({ components={{ br:
, lnk: ( - + Omat varaukset -sivulta ), @@ -179,7 +179,7 @@ function ReservationConfirmation({ )} {reservationUnit != null && ( { marginTop: "var(--spacing-3-xl)", }} > - + {t("reservations:backToSearch")} diff --git a/apps/ui/components/reservation/ReservationInfoCard.tsx b/apps/ui/components/reservation/ReservationInfoCard.tsx index 134360936f..1269aedc6f 100644 --- a/apps/ui/components/reservation/ReservationInfoCard.tsx +++ b/apps/ui/components/reservation/ReservationInfoCard.tsx @@ -25,8 +25,8 @@ import { getTranslation, formatDateTimeRange, } from "@/modules/util"; -import { reservationUnitPath } from "@/modules/const"; import { getImageSource } from "common/src/helpers"; +import { getReservationUnitPath } from "@/modules/urls"; type Type = "pending" | "confirmed" | "complete"; @@ -164,10 +164,6 @@ export function ReservationInfoCard({ const img = getMainImage(reservationUnit); const imgSrc = getImageSource(img, "medium"); - const link = reservationUnit.pk - ? reservationUnitPath(reservationUnit.pk) - : ""; - // Have to make client only because date formatting doesn't work on server side return ( @@ -176,7 +172,7 @@ export function ReservationInfoCard({ {name} @@ -189,6 +185,12 @@ export function ReservationInfoCard({ )} + + {t("reservations:reservationNumber")}:{" "} + + {reservation.pk ?? "-"} + + {reservationUnit.unit != null ? getTranslation(reservationUnit.unit, "name") diff --git a/apps/ui/components/reservations/UnpaidReservationNotification.tsx b/apps/ui/components/reservations/UnpaidReservationNotification.tsx index b26d4380e1..319e992778 100644 --- a/apps/ui/components/reservations/UnpaidReservationNotification.tsx +++ b/apps/ui/components/reservations/UnpaidReservationNotification.tsx @@ -16,10 +16,10 @@ import { BlackButton } from "@/styles/util"; import { useOrder, useDeleteReservation } from "@/hooks/reservation"; import { getCheckoutUrl } from "@/modules/reservation"; import { filterNonNullable } from "common/src/helpers"; -import { reservationUnitPrefix } from "@/modules/const"; import { ApolloError } from "@apollo/client"; import { toApiDate } from "common/src/common/util"; import { errorToast, successToast } from "common/src/common/toast"; +import { getReservationInProgressPath } from "@/modules/urls"; type QueryT = NonNullable; type EdgeT = NonNullable; @@ -275,10 +275,11 @@ export function InProgressReservationNotification() { }; const handleContinue = (reservation?: NodeT) => { - // TODO add an url builder for this - // - reuse the url builder in [...params].tsx const reservationUnit = reservation?.reservationUnits?.find(() => true); - const url = `${reservationUnitPrefix}/${reservationUnit?.pk}/reservation/${reservation?.pk}`; + const url = getReservationInProgressPath( + reservationUnit?.pk, + reservation?.pk + ); router.push(url); }; diff --git a/apps/ui/components/search/ReservationUnitCard.tsx b/apps/ui/components/search/ReservationUnitCard.tsx index 5fc35b8d97..1998b42b9c 100644 --- a/apps/ui/components/search/ReservationUnitCard.tsx +++ b/apps/ui/components/search/ReservationUnitCard.tsx @@ -10,10 +10,11 @@ import { useTranslation } from "next-i18next"; import NextImage from "next/image"; import type { ReservationUnitCardFieldsFragment } from "@gql/gql-types"; import { getMainImage, getTranslation } from "@/modules/util"; -import { reservationUnitPrefix } from "@/modules/const"; import { getReservationUnitName, getUnitName } from "@/modules/reservationUnit"; import { getImageSource } from "common/src/helpers"; import Card from "common/src/components/Card"; +import { getReservationUnitPath } from "@/modules/urls"; +import { ButtonLikeLink } from "../common/ButtonLikeLink"; type Node = ReservationUnitCardFieldsFragment; interface IProps { @@ -29,13 +30,10 @@ export function ReservationUnitCard({ containsReservationUnit, removeReservationUnit, }: IProps): JSX.Element { - const { t, i18n } = useTranslation(); + const { t } = useTranslation(); const name = getReservationUnitName(reservationUnit); - const localeString = i18n.language === "fi" ? "" : `/${i18n.language}`; - const link = `${localeString}${reservationUnitPrefix}/${reservationUnit.pk}`; - const unitName = reservationUnit.unit ? getUnitName(reservationUnit.unit) : "-"; @@ -108,15 +106,16 @@ export function ReservationUnitCard({ ); } buttons.push( - } - onClick={() => window.open(link, "_blank")} + + {t("reservationUnitCard:seeMore")} - +
); return ( diff --git a/apps/ui/components/search/SingleSearchReservationUnitCard.tsx b/apps/ui/components/search/SingleSearchReservationUnitCard.tsx index 7038e469c9..63bcbaedc4 100644 --- a/apps/ui/components/search/SingleSearchReservationUnitCard.tsx +++ b/apps/ui/components/search/SingleSearchReservationUnitCard.tsx @@ -15,12 +15,13 @@ import { getReservationUnitName, getUnitName, } from "@/modules/reservationUnit"; -import { isBrowser, reservationUnitPrefix } from "@/modules/const"; +import { isBrowser } from "@/modules/const"; import { ButtonLikeLink } from "../common/ButtonLikeLink"; import { getImageSource } from "common/src/helpers"; import Card from "common/src/components/Card"; import Tag from "common/src/components/Tag"; import { useSearchParams } from "next/navigation"; +import { getReservationUnitPath } from "@/modules/urls"; type QueryT = NonNullable; type Edge = NonNullable[0]>; @@ -87,7 +88,7 @@ function useConstructLink( } const linkURL = new URL( - `${reservationUnitPrefix}/${reservationUnit.pk}`, + getReservationUnitPath(reservationUnit.pk), document.baseURI ); if (duration != null) linkURL.searchParams.set("duration", duration); diff --git a/apps/ui/modules/const.ts b/apps/ui/modules/const.ts index 5cd60f37f7..00008b0174 100644 --- a/apps/ui/modules/const.ts +++ b/apps/ui/modules/const.ts @@ -3,20 +3,10 @@ import type { TFunction } from "i18next"; export { isBrowser } from "common/src/helpers"; export { genericTermsVariant } from "common/src/const"; -export const reservationUnitPrefix = "/reservation-unit"; -export const searchPrefix = "/search"; -export const singleSearchPrefix = "/search/single"; -export const applicationsPrefix = "/applications"; -export const reservationsPrefix = "/reservations"; -export const recurringReservationsPrefix = "/recurring"; - export const mapUrlPrefix = "https://palvelukartta.hel.fi/"; export const SEARCH_PAGING_LIMIT = 36; -export const reservationUnitPath = (id: number): string => - `${reservationUnitPrefix}/${id}`; - export const participantCountOptions = [ 1, 2, 5, 10, 15, 20, 25, 30, 40, 50, 60, 80, 100, 150, 200, ].map((v) => ({ label: `${v}`, value: v })); diff --git a/apps/ui/modules/urls.ts b/apps/ui/modules/urls.ts new file mode 100644 index 0000000000..ccbf56f0a6 --- /dev/null +++ b/apps/ui/modules/urls.ts @@ -0,0 +1,85 @@ +import { type Maybe } from "@/gql/gql-types"; + +export const searchPrefix = "/search"; +export const reservationUnitPrefix = "/reservation-unit"; +export const singleSearchPrefix = "/search/single"; +export const applicationsPrefix = "/applications"; +export const reservationsPrefix = "/reservations"; +export const seasonalPrefix = "/recurring"; + +export const applicationsPath = `${applicationsPrefix}/`; +export const reservationsPath = `${reservationsPrefix}/`; + +export function getApplicationRoundPath( + pk: Maybe | undefined, + page?: string | undefined +): string { + if (pk == null) { + return ""; + } + return `${seasonalPrefix}/${pk}/${page ?? ""}`; +} + +export function getSeasonalSearchPath( + pk: Maybe | undefined, + params?: URLSearchParams +): string { + if (pk == null) { + return ""; + } + const base = `${seasonalPrefix}/${pk}`; + + if (params && Object.keys(params).length > 0) { + return `${base}?${params.toString()}`; + } + + return base; +} + +export function getSingleSearchPath(params?: URLSearchParams): string { + const base = `${singleSearchPrefix}/`; + + if (params && Object.keys(params).length > 0) { + return `${base}?${params.toString()}`; + } + + return base; +} + +type ApplicationPages = "page1" | "page2" | "page3" | "view" | "preview"; +export function getApplicationPath( + pk: Maybe | undefined, + page?: ApplicationPages | undefined +): string { + if (pk == null) { + return ""; + } + return `${applicationsPrefix}/${pk}/${page ?? ""}`; +} + +export function getReservationPath( + pk: Maybe | undefined, + page?: string | undefined +): string { + if (pk == null) { + return ""; + } + return `${reservationsPrefix}/${pk}/${page ?? ""}`; +} + +export function getReservationInProgressPath( + pk: Maybe | undefined, + reservationPk: Maybe | undefined +): string { + if (pk == null || reservationPk == null) { + return ""; + } + return `${reservationsPrefix}/${pk}/reservation/${reservationPk}`; +} + +export function getReservationUnitPath(pk: Maybe | undefined): string { + if (pk == null) { + return ""; + } + return `${reservationUnitPrefix}/${pk}`; +} diff --git a/apps/ui/modules/util.ts b/apps/ui/modules/util.ts index 85194a9415..87e9a2c16d 100644 --- a/apps/ui/modules/util.ts +++ b/apps/ui/modules/util.ts @@ -1,6 +1,5 @@ import { isSameDay, parseISO } from "date-fns"; import { i18n, TFunction } from "next-i18next"; -import queryString from "query-string"; import { trim } from "lodash"; import type { ApolloError } from "@apollo/client"; import type { OptionType } from "common"; @@ -16,13 +15,7 @@ import type { ImageFragment, LocationFieldsI18nFragment, } from "@gql/gql-types"; -import { - searchPrefix, - applicationsPrefix, - singleSearchPrefix, - reservationsPrefix, - isBrowser, -} from "./const"; +import { isBrowser } from "./const"; import { type LocalizationLanguages } from "common/src/helpers"; export { formatDuration } from "common/src/common/util"; @@ -149,38 +142,6 @@ export const getComboboxValues = ( return []; }; -type SearchParams = Record< - string, - string | (string | null)[] | number | boolean | null ->; - -// @deprecated, todo rewrite a getSearcSeasonalhUrl function -// or alternatively remove the use of query-string -export const searchUrl = (params: SearchParams): string => { - const response = `${searchPrefix}/`; - - if (params && Object.keys(params).length > 0) { - return `${response}?${queryString.stringify(params)}`; - } - - return response; -}; - -// @deprecated, todo rewrite a getSingleSearchUrl function -// or alternatively remove the use of query-string -export const singleSearchUrl = (params?: SearchParams): string => { - const response = `${singleSearchPrefix}/`; - - if (params && Object.keys(params).length > 0) { - return `${response}?${queryString.stringify(params)}`; - } - - return response; -}; - -export const applicationsUrl = `${applicationsPrefix}/`; -export const reservationsUrl = `${reservationsPrefix}/`; - const imagePriority = ["main", "map", "ground_plan", "other"].map((n) => n.toUpperCase() ); diff --git a/apps/ui/pages/application/[id]/sent.tsx b/apps/ui/pages/application/[id]/sent.tsx index 926670b23d..6dbd714434 100644 --- a/apps/ui/pages/application/[id]/sent.tsx +++ b/apps/ui/pages/application/[id]/sent.tsx @@ -7,7 +7,7 @@ import { breakpoints } from "common/src/common/style"; import { Container } from "common"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import type { GetServerSidePropsContext } from "next"; -import { applicationsUrl } from "@/modules/util"; +import { applicationsPath } from "@/modules/urls"; import Head from "@/components/application/Head"; import { getCommonServerSideProps } from "@/modules/serverUtils"; @@ -36,7 +36,7 @@ const Sent = (): JSX.Element => { {t("application:sent.body")} router.push(applicationsUrl)} + onClick={() => router.push(applicationsPath)} iconRight={} size="small" > @@ -47,7 +47,7 @@ const Sent = (): JSX.Element => { ); }; -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { +export async function getServerSideProps(ctx: GetServerSidePropsContext) { const { locale } = ctx; // TODO should fetch on SSR but we need authentication for it @@ -64,6 +64,6 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { ...(await serverSideTranslations(locale ?? "fi")), }, }; -}; +} export default Sent; diff --git a/apps/ui/pages/application/[id]/view.tsx b/apps/ui/pages/application/[id]/view.tsx index cbd6e09850..b31cad0f3c 100644 --- a/apps/ui/pages/application/[id]/view.tsx +++ b/apps/ui/pages/application/[id]/view.tsx @@ -17,7 +17,7 @@ import { import { base64encode } from "common/src/helpers"; import { useApplicationQuery } from "@gql/gql-types"; -const View = ({ id: pk, tos }: Props): JSX.Element => { +function View({ id: pk, tos }: Props): JSX.Element { const { t } = useTranslation(); const router = useRouter(); @@ -66,11 +66,11 @@ const View = ({ id: pk, tos }: Props): JSX.Element => { ); -}; +} type Props = Awaited>["props"]; -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { +export async function getServerSideProps(ctx: GetServerSidePropsContext) { const { locale } = ctx; const commonProps = getCommonServerSideProps(); const apolloClient = createApolloClient(commonProps.apiBaseUrl, ctx); @@ -92,6 +92,6 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { ...(await serverSideTranslations(locale ?? "fi")), }, }; -}; +} export default View; diff --git a/apps/ui/pages/recurring/index.tsx b/apps/ui/pages/recurring/index.tsx index 649945e9c3..0f37e0d922 100644 --- a/apps/ui/pages/recurring/index.tsx +++ b/apps/ui/pages/recurring/index.tsx @@ -14,7 +14,7 @@ import { } from "@gql/gql-types"; import { filterNonNullable } from "common/src/helpers"; import { HeroSubheading } from "@/modules/style/typography"; -import ApplicationRoundCard from "@/components/recurring/ApplicationRoundCard"; +import { ApplicationRoundCard } from "@/components/recurring/ApplicationRoundCard"; import { createApolloClient } from "@/modules/apolloClient"; import { getCommonServerSideProps } from "@/modules/serverUtils"; diff --git a/apps/ui/pages/reservation-unit/[...params].tsx b/apps/ui/pages/reservation-unit/[...params].tsx index 14526e3846..a9159c584c 100644 --- a/apps/ui/pages/reservation-unit/[...params].tsx +++ b/apps/ui/pages/reservation-unit/[...params].tsx @@ -27,8 +27,7 @@ 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 { reservationUnitPrefix, reservationsPrefix } from "@/modules/const"; -import { reservationsUrl } from "@/modules/util"; +import { getReservationPath, getReservationUnitPath } from "@/modules/urls"; import Sanitize from "@/components/common/Sanitize"; import { isReservationUnitFreeOfCharge } from "@/modules/reservationUnit"; import { @@ -197,7 +196,7 @@ function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null { const [deleteReservation] = useDeleteReservationMutation({ errorPolicy: "all", onError: () => { - router.push(`${reservationUnitPrefix}/${reservationUnit?.pk}`); + router.push(getReservationUnitPath(reservationUnit?.pk)); }, }); @@ -279,7 +278,7 @@ function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null { }, }); if (data?.updateReservation?.state === "CANCELLED") { - router.push(`${reservationUnitPrefix}/${reservationUnit?.pk}`); + router.push(getReservationUnitPath(reservationUnit?.pk)); } else { await refetch(); setStep(1); @@ -316,7 +315,7 @@ function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null { state === ReservationStateChoice.Confirmed || state === ReservationStateChoice.RequiresHandling ) { - router.push(`${reservationsUrl}${pk}/confirmation`); + router.push(getReservationPath(pk, "confirmation")); } else if (steps?.length > 2) { const { order } = data?.confirmReservation ?? {}; const checkoutUrl = getCheckoutUrl(order, i18n.language); @@ -346,7 +345,7 @@ function ReservationUnitReservation(props: PropsNarrowed): JSX.Element | null { // NOTE: only navigate away from the page if the reservation is cancelled the confirmation hook handles delete const cancelReservation = useCallback(async () => { - router.push(`${reservationUnitPrefix}/${reservationUnit?.pk}`); + router.push(getReservationUnitPath(reservationUnit?.pk)); }, [router, reservationUnit?.pk]); const generalFields = getGeneralFields({ supportedFields, reservation }); @@ -500,7 +499,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) { return { redirect: { permanent: false, - destination: `${reservationsPrefix}/${reservation.pk}`, + destination: getReservationPath(reservation.pk), }, props: { notFound: true, // for prop narrowing diff --git a/apps/ui/pages/reservations/[id]/cancel.tsx b/apps/ui/pages/reservations/[id]/cancel.tsx index 5f4a95b9d3..81d5ba3d85 100644 --- a/apps/ui/pages/reservations/[id]/cancel.tsx +++ b/apps/ui/pages/reservations/[id]/cancel.tsx @@ -15,8 +15,8 @@ import { getCommonServerSideProps } from "@/modules/serverUtils"; import { createApolloClient } from "@/modules/apolloClient"; import { base64encode, filterNonNullable } from "common/src/helpers"; import { isReservationCancellable } from "@/modules/reservation"; -import { reservationsPrefix } from "@/modules/const"; import { CURRENT_USER } from "@/modules/queries/user"; +import { getReservationPath } from "@/modules/urls"; type PropsNarrowed = Exclude; @@ -92,7 +92,7 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) { return { redirect: { permanent: true, - destination: `${reservationsPrefix}/${reservation.pk}`, + destination: getReservationPath(reservation.pk), }, props: { notFound: true, // for prop narrowing diff --git a/apps/ui/pages/reservations/[id]/edit.tsx b/apps/ui/pages/reservations/[id]/edit.tsx index 5458b518a0..6495592919 100644 --- a/apps/ui/pages/reservations/[id]/edit.tsx +++ b/apps/ui/pages/reservations/[id]/edit.tsx @@ -28,7 +28,6 @@ import { StepState, Stepper } from "hds-react"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { errorToast } from "common/src/common/toast"; -import { reservationsPrefix } from "@/modules/const"; import { EditStep0 } from "@/components/reservation/EditStep0"; import { EditStep1 } from "@/components/reservation/EditStep1"; import { @@ -46,6 +45,7 @@ import { transformReservation, } from "@/modules/reservation"; import { NotModifiableReason } from "@/components/reservation/NotModifiableReason"; +import { getReservationPath } from "@/modules/urls"; type Props = Awaited>["props"]; type PropsNarrowed = Exclude; @@ -123,7 +123,7 @@ function ReservationEditPage(props: PropsNarrowed): JSX.Element { } try { await adjustReservationTime({ pk: reservation.pk, ...times }); - router.push(`${reservationsPrefix}/${reservation.pk}/?timeUpdated=true`); + router.push(`${getReservationPath(reservation.pk)}?timeUpdated=true`); } catch (e) { if (e instanceof Error) { // TODO don't print the error message to the user diff --git a/apps/ui/pages/reservations/[id]/index.tsx b/apps/ui/pages/reservations/[id]/index.tsx index 1ec25fc244..43b6fc7e99 100644 --- a/apps/ui/pages/reservations/[id]/index.tsx +++ b/apps/ui/pages/reservations/[id]/index.tsx @@ -28,13 +28,8 @@ import { import Link from "next/link"; import { Container } from "common"; import { useOrder } from "@/hooks/reservation"; -import { reservationUnitPath } from "@/modules/const"; import { createApolloClient } from "@/modules/apolloClient"; -import { - formatDateTimeRange, - getTranslation, - reservationsUrl, -} from "@/modules/util"; +import { formatDateTimeRange, getTranslation } from "@/modules/util"; import { CenterSpinner } from "@/components/common/common"; import Sanitize from "@/components/common/Sanitize"; import { AccordionWithState as Accordion } from "@/components/common/Accordion"; @@ -66,6 +61,7 @@ import { ButtonLikeLink } from "@/components/common/ButtonLikeLink"; import { useRouter } from "next/router"; import { successToast } from "common/src/common/toast"; import { ReservationPageWrapper } from "@/components/reservations/styles"; +import { getReservationPath, getReservationUnitPath } from "@/modules/urls"; type PropsNarrowed = Exclude; @@ -457,7 +453,7 @@ function Reservation({ {getReservationUnitName(reservationUnit)} @@ -520,7 +516,7 @@ function Reservation({ {canTimeBeModified && ( {t("reservations:modifyReservationTime")} @@ -529,9 +525,8 @@ function Reservation({ )} {isCancellable && ( {t( From 01362fdab1b7c7ec4f72a9f4a9c973bcb525dd47 Mon Sep 17 00:00:00 2001 From: Joonatan Kuosa Date: Fri, 4 Oct 2024 11:58:56 +0300 Subject: [PATCH 04/16] refactor: seasonal reservation urls - move criteria and search pages under the application round --- .../components/search/SeasonalSearchForm.tsx | 17 +++++---------- apps/ui/modules/urls.ts | 1 - .../[id]/criteria/index.tsx} | 0 .../{search => recurring/[id]}/index.tsx | 21 +++++++++---------- 4 files changed, 15 insertions(+), 24 deletions(-) rename apps/ui/pages/{criteria/[id].tsx => recurring/[id]/criteria/index.tsx} (100%) rename apps/ui/pages/{search => recurring/[id]}/index.tsx (93%) diff --git a/apps/ui/components/search/SeasonalSearchForm.tsx b/apps/ui/components/search/SeasonalSearchForm.tsx index 168632984b..04d9f8bf33 100644 --- a/apps/ui/components/search/SeasonalSearchForm.tsx +++ b/apps/ui/components/search/SeasonalSearchForm.tsx @@ -164,17 +164,16 @@ function mapQueryToForm(query: ParsedUrlQuery): FormValues { }; } +type OptionType = { value: number; label: string }; export function SeasonalSearchForm({ - applicationRoundOptions, reservationUnitTypeOptions, purposeOptions, unitOptions, isLoading, }: { - applicationRoundOptions: Array<{ value: number; label: string }>; - reservationUnitTypeOptions: Array<{ value: number; label: string }>; - purposeOptions: Array<{ value: number; label: string }>; - unitOptions: Array<{ value: number; label: string }>; + reservationUnitTypeOptions: OptionType[]; + purposeOptions: OptionType[]; + unitOptions: OptionType[]; isLoading: boolean; }): JSX.Element | null { const { t } = useTranslation(); @@ -211,18 +210,12 @@ export function SeasonalSearchForm({ }; const multiSelectFilters = ["unit", "reservationUnitTypes", "purposes"]; - const hideList = ["applicationRound", "order", "sort", "ref"]; + const hideList = ["id", "order", "sort", "ref"]; return (
- >["props"]; @@ -114,10 +118,13 @@ function SeasonalSearch({ reservationUnitTypeOptions, purposeOptions, }: Props): JSX.Element { + const { t, i18n } = useTranslation(); + const router = useRouter(); const searchValues = useSearchValues(); + const applicationRoundPk = mapQueryParamToNumber(router.query.id); const selectedApplicationRound = applicationRounds.find( - (ar) => ar.pk === Number(searchValues.applicationRound) + (ar) => ar.pk === applicationRoundPk ); const { reservationUnits: selectedReservationUnits, @@ -128,8 +135,6 @@ function SeasonalSearch({ // Hide other application rounds' reservation units } = useReservationUnitsList(selectedApplicationRound); - const { t, i18n } = useTranslation(); - const variables = processVariables( searchValues, i18n.language, @@ -144,11 +149,6 @@ function SeasonalSearch({ ); const pageInfo = currData?.reservationUnits?.pageInfo; - const applicationRoundOptions = applicationRounds.map((applicationRound) => ({ - value: applicationRound.pk ?? 0, - label: getApplicationRoundName(applicationRound), - })); - return ( {error ? ( @@ -162,7 +162,6 @@ function SeasonalSearch({ {t("search:recurring.heading")} {t("search:recurring.text")} Date: Fri, 4 Oct 2024 13:15:04 +0300 Subject: [PATCH 05/16] refactor: move application pages - use `applications` base url for all application page - remove unnecessary intro page --- .../applications/ApplicationCard.tsx | 8 +- .../applications/ApplicationsGroup.tsx | 6 +- .../components/common/StartApplicationBar.tsx | 74 +++++--- .../components/search/SeasonalSearchForm.tsx | 2 - apps/ui/hooks/useSearchValues.tsx | 17 +- apps/ui/modules/search.ts | 4 +- apps/ui/modules/urls.ts | 6 +- apps/ui/modules/util.ts | 2 - .../[...params].tsx | 17 +- .../[id]/page3.tsx | 0 .../[id]/preview.tsx | 0 .../[id]/sent.tsx | 0 .../[id]/view.tsx | 0 .../index.tsx} | 0 apps/ui/pages/intro.tsx | 168 ------------------ 15 files changed, 78 insertions(+), 226 deletions(-) rename apps/ui/pages/{application => applications}/[...params].tsx (95%) rename apps/ui/pages/{application => applications}/[id]/page3.tsx (100%) rename apps/ui/pages/{application => applications}/[id]/preview.tsx (100%) rename apps/ui/pages/{application => applications}/[id]/sent.tsx (100%) rename apps/ui/pages/{application => applications}/[id]/view.tsx (100%) rename apps/ui/pages/{applications.tsx => applications/index.tsx} (100%) delete mode 100644 apps/ui/pages/intro.tsx diff --git a/apps/ui/components/applications/ApplicationCard.tsx b/apps/ui/components/applications/ApplicationCard.tsx index abbc505451..c461ad101f 100644 --- a/apps/ui/components/applications/ApplicationCard.tsx +++ b/apps/ui/components/applications/ApplicationCard.tsx @@ -19,7 +19,7 @@ import { useCancelApplicationMutation, type ApplicationsQuery, } from "@gql/gql-types"; -import { applicationUrl, formatDateTime } from "@/modules/util"; +import { formatDateTime } from "@/modules/util"; import { getApplicationRoundName } from "@/modules/applicationRound"; import { ButtonLikeLink } from "@/components/common/ButtonLikeLink"; import { ConfirmationDialog } from "common/src/components/ConfirmationDialog"; @@ -182,11 +182,7 @@ function ApplicationCard({ application, actionCallback }: Props): JSX.Element { , diff --git a/apps/ui/components/applications/ApplicationsGroup.tsx b/apps/ui/components/applications/ApplicationsGroup.tsx index 7f0245ea0a..133f25d6b2 100644 --- a/apps/ui/components/applications/ApplicationsGroup.tsx +++ b/apps/ui/components/applications/ApplicationsGroup.tsx @@ -25,11 +25,11 @@ const Wrapper = styled.div` margin-bottom: var(--spacing-layout-l); `; -const ApplicationsGroup = ({ +function ApplicationsGroup({ name, applications, actionCallback, -}: Props): JSX.Element | null => { +}: Props): JSX.Element | null { if (applications.length === 0 || !applications[0].applicationRound == null) { return null; } @@ -50,6 +50,6 @@ const ApplicationsGroup = ({ ))} ); -}; +} export default ApplicationsGroup; diff --git a/apps/ui/components/common/StartApplicationBar.tsx b/apps/ui/components/common/StartApplicationBar.tsx index 76e562d5ef..7b2b75f8ae 100644 --- a/apps/ui/components/common/StartApplicationBar.tsx +++ b/apps/ui/components/common/StartApplicationBar.tsx @@ -7,8 +7,13 @@ import { Container as CommonContainer } from "common"; import ClientOnly from "common/src/ClientOnly"; import { JustForDesktop, JustForMobile } from "@/modules/style/layout"; import { truncatedText } from "@/styles/util"; -import Link from "next/link"; -import { focusStyles } from "common/styles/cssFragments"; +import { useRouter } from "next/router"; +import { + ApplicationCreateMutationInput, + useCreateApplicationMutation, +} from "@/gql/gql-types"; +import { errorToast } from "common/src/common/toast"; +import { getApplicationPath } from "@/modules/urls"; type Props = { count: number; @@ -78,28 +83,43 @@ const DeleteButton = styled(Button).attrs({ ${truncatedText} `; -const StyledLink = styled(Link)` - background-color: transparent; - color: var(--color-white); - ${truncatedText} - display: flex; - gap: var(--spacing-2-xs); - ${focusStyles} - padding: var(--spacing-2-xs) var(--spacing-s); - && { - --color-focus: var(--color-white); - --focus-outline-color: var(--color-white); - } - &:hover { - text-decoration: underline; - } -`; - -const StartApplicationBar = ({ +function StartApplicationBar({ count, clearSelections, -}: Props): JSX.Element | null => { +}: Props): JSX.Element | null { const { t } = useTranslation(); + const router = useRouter(); + + const [create, { loading: isSaving }] = useCreateApplicationMutation(); + + const createNewApplication = async (applicationRoundId: number) => { + const input: ApplicationCreateMutationInput = { + applicationRound: applicationRoundId, + }; + try { + const { data } = await create({ + variables: { input }, + }); + + if (data?.createApplication?.pk) { + const { pk } = data.createApplication; + router.replace(getApplicationPath(pk, "page1")); + } else { + throw new Error("create application mutation failed"); + } + } catch (e) { + errorToast({ text: t("application:Intro.createFailedContent") }); + } + }; + + const onNext = () => { + const applicationRoundId = router.query.id; + if (typeof applicationRoundId === "string" && applicationRoundId !== "") { + createNewApplication(Number(applicationRoundId)); + } else { + throw new Error("Application round id is missing"); + } + }; // This breaks SSR because the server knowns nothing about client side stores // we can't fix it with CSS since it doesn't update properly @@ -135,16 +155,20 @@ const StartApplicationBar = ({ - + ); -}; +} const StartApplicationBarWrapped = (props: Props) => ( diff --git a/apps/ui/components/search/SeasonalSearchForm.tsx b/apps/ui/components/search/SeasonalSearchForm.tsx index 04d9f8bf33..d876d03e42 100644 --- a/apps/ui/components/search/SeasonalSearchForm.tsx +++ b/apps/ui/components/search/SeasonalSearchForm.tsx @@ -140,7 +140,6 @@ const filterOrder = [ ]; type FormValues = { - applicationRound: number; minPersons: number | null; maxPersons: number | null; unit: number[]; @@ -152,7 +151,6 @@ type FormValues = { // TODO combine as much as possible with the one in single-search (move them to a common place) function mapQueryToForm(query: ParsedUrlQuery): FormValues { return { - applicationRound: mapQueryParamToNumber(query.applicationRound) ?? 0, purposes: mapQueryParamToNumberArray(query.purposes), unit: mapQueryParamToNumberArray(query.unit), reservationUnitTypes: mapQueryParamToNumberArray( diff --git a/apps/ui/hooks/useSearchValues.tsx b/apps/ui/hooks/useSearchValues.tsx index fbc43ea473..3fe19ef058 100644 --- a/apps/ui/hooks/useSearchValues.tsx +++ b/apps/ui/hooks/useSearchValues.tsx @@ -1,5 +1,6 @@ import { useRouter } from "next/router"; -import { ParsedUrlQuery } from "node:querystring"; +import { type ParsedUrlQuery } from "node:querystring"; +import { type UrlObject } from "node:url"; // TODO should take a list of keys or use a type instead of a record so we can remove invalid keys export function useSearchValues() { @@ -38,9 +39,16 @@ export function useSearchModify() { order: newOrder, ...(force ? { ref: nextRef } : {}), }; - router.replace({ - query: newValues, - }); + + // NOTE without this next router can't handle [id] pages + const url: UrlObject = { + pathname: router.pathname, + query: { + ...router.query, + ...newValues, + }, + }; + router.replace(url, undefined, { shallow: true }); }; /// @param hideList - list of keys to ignore when resetting the query @@ -52,6 +60,7 @@ export function useSearchModify() { } return acc; }, {}); + // NOTE for some reason we don't have to fix [id] pages here router.replace({ query: newValues, }); diff --git a/apps/ui/modules/search.ts b/apps/ui/modules/search.ts index e049605337..df98f21477 100644 --- a/apps/ui/modules/search.ts +++ b/apps/ui/modules/search.ts @@ -211,8 +211,8 @@ export function processVariables( showOnlyReservable: true, } : {}), - ...(values.applicationRound != null && isSeasonal - ? { applicationRound: paramToIntegers(values.applicationRound) } + ...(values.id != null && isSeasonal + ? { applicationRound: paramToIntegers(values.id) } : {}), first: SEARCH_PAGING_LIMIT, orderBy, diff --git a/apps/ui/modules/urls.ts b/apps/ui/modules/urls.ts index 3b20eb08de..ef21139a2a 100644 --- a/apps/ui/modules/urls.ts +++ b/apps/ui/modules/urls.ts @@ -10,13 +10,13 @@ export const applicationsPath = `${applicationsPrefix}/`; export const reservationsPath = `${reservationsPrefix}/`; export function getApplicationRoundPath( - pk: Maybe | undefined, + id: Maybe | undefined, page?: string | undefined ): string { - if (pk == null) { + if (id == null) { return ""; } - return `${seasonalPrefix}/${pk}/${page ?? ""}`; + return `${seasonalPrefix}/${id}/${page ?? ""}`; } export function getSeasonalSearchPath( diff --git a/apps/ui/modules/util.ts b/apps/ui/modules/util.ts index 87e9a2c16d..373675a150 100644 --- a/apps/ui/modules/util.ts +++ b/apps/ui/modules/util.ts @@ -192,8 +192,6 @@ export const getAddressAlt = (ru: { return trim(`${street}, ${city}`, ", "); }; -export const applicationUrl = (id: number): string => `/application/${id}`; - export const applicationErrorText = ( t: TFunction, key: string | undefined, diff --git a/apps/ui/pages/application/[...params].tsx b/apps/ui/pages/applications/[...params].tsx similarity index 95% rename from apps/ui/pages/application/[...params].tsx rename to apps/ui/pages/applications/[...params].tsx index bc03db8579..77818fcda9 100644 --- a/apps/ui/pages/application/[...params].tsx +++ b/apps/ui/pages/applications/[...params].tsx @@ -29,6 +29,7 @@ import { type ApplicationQueryVariables, } from "common/gql/gql-types"; import { errorToast } from "common/src/common/toast"; +import { getApplicationPath } from "@/modules/urls"; // TODO move this to a shared file // and combine all the separate error handling functions to one @@ -160,7 +161,6 @@ export async function getServerSideProps(ctx: GetServerSidePropsContext) { function ApplicationRootPage({ slug, data }: PropsNarrowed): JSX.Element { const { application, applicationRound } = data; - const pageId = slug; const router = useRouter(); const [update, { error }] = useApplicationUpdate(); @@ -173,21 +173,16 @@ function ApplicationRootPage({ slug, data }: PropsNarrowed): JSX.Element { console.error("application pk is 0"); return 0; } - - const input = transformApplication(appToSave); - const pk = await update(input); - return pk; + return update(transformApplication(appToSave)); }; const saveAndNavigate = - (path: string) => async (appToSave: ApplicationFormValues) => { + (path: "page2" | "page3") => async (appToSave: ApplicationFormValues) => { const pk = await handleSave(appToSave); if (pk === 0) { return; } - const prefix = `/application/${pk}`; - const target = `${prefix}/${path}`; - router.push(target); + router.push(getApplicationPath(pk, path)); }; const { reservationUnits: selectedReservationUnits } = @@ -231,7 +226,7 @@ function ApplicationRootPage({ slug, data }: PropsNarrowed): JSX.Element { return ( - {pageId === "page1" ? ( + {slug === "page1" ? ( - ) : pageId === "page2" ? ( + ) : slug === "page2" ? ( >["props"]; - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - const { locale } = ctx; - const commonProps = getCommonServerSideProps(); - const apolloClient = createApolloClient(commonProps.apiBaseUrl, ctx); - - const { data } = await apolloClient.query< - ApplicationRoundsUiQuery, - ApplicationRoundsUiQueryVariables - >({ - query: ApplicationRoundsUiDocument, - fetchPolicy: "no-cache", - }); - - const applicationRounds = filterNonNullable( - data?.applicationRounds?.edges?.map((n) => n?.node) - ); - - return { - props: { - ...commonProps, - ...(await serverSideTranslations(locale ?? "fi")), - applicationRounds, - }, - }; -}; - -const Container = styled.div` - margin-top: var(--spacing-layout-m); - padding-bottom: var(--spacing-layout-xl); - font-size: var(--fontsize-body-m); - gap: var(--spacing-l); - display: grid; - grid-template-columns: 1fr 382px; - - /* stylelint-disable-next-line */ - #applicationRoundSelect-label { - display: none; - } - - @media (max-width: ${breakpoints.l}) { - grid-template-columns: 1fr; - } -`; - -const IntroPage = ({ applicationRounds }: Props): JSX.Element => { - const history = useRouter(); - const { t } = useTranslation(); - - const [error, setError] = useState(undefined); - const [applicationRound, setApplicationRound] = useState(0); - - const applicationRoundOptions = - applicationRounds - .filter((ar) => ar.status === ApplicationRoundStatusChoice.Open) - .map((ar) => ({ - value: ar.pk ?? 0, - label: getApplicationRoundName(ar), - })) ?? []; - - const [create, { loading: isSaving }] = useCreateApplicationMutation({ - onError: (e) => { - // eslint-disable-next-line no-console - console.warn("create application mutation failed: ", e); - setError(t("application:Intro.createFailedContent")); - }, - }); - - const createNewApplication = async (applicationRoundId: number) => { - const input: ApplicationCreateMutationInput = { - applicationRound: applicationRoundId, - }; - const { data: mutResponse, errors } = await create({ - variables: { input }, - }); - - if (errors) { - // eslint-disable-next-line no-console - console.error("create application mutation failed: ", errors); - } else if (mutResponse?.createApplication?.pk) { - const { pk } = mutResponse.createApplication; - history.replace(`/application/${pk}/page1`); - } else { - // eslint-disable-next-line no-console - console.error( - "create application mutation failed: ", - mutResponse?.createApplication - ); - } - }; - - return ( - <> - - - {applicationRounds.length > 0 ? ( - <> - - id="applicationRoundSelect" - placeholder={t("common:select")} - options={applicationRoundOptions} - label={t("common:select")} - onChange={(selection: { value: number; label: string }) => { - setApplicationRound(selection.value); - }} - value={ - applicationRoundOptions.find( - (n) => n.value === applicationRound - ) ?? null - } - /> - { - createNewApplication(applicationRound); - }} - > - {t("application:Intro.startNewApplication")} - - - ) : ( - - )} - - - {error != null ? ( - setError(undefined)} - > - {error} - - ) : null} - - ); -}; - -export default IntroPage; From 229b86b2522ab54609b3086ee47fb8afc7a6428e Mon Sep 17 00:00:00 2001 From: Joonatan Kuosa Date: Mon, 7 Oct 2024 11:19:30 +0300 Subject: [PATCH 06/16] fix: broken urls from application page moves --- .../application/ApplicationPage.tsx | 6 +- apps/ui/components/application/Buttons.tsx | 35 --------- apps/ui/modules/urls.ts | 8 +- apps/ui/pages/applications/[id]/page3.tsx | 73 ++++++++++--------- apps/ui/pages/applications/[id]/preview.tsx | 68 ++++++----------- 5 files changed, 73 insertions(+), 117 deletions(-) delete mode 100644 apps/ui/components/application/Buttons.tsx diff --git a/apps/ui/components/application/ApplicationPage.tsx b/apps/ui/components/application/ApplicationPage.tsx index 5d7d229538..3213bf41ba 100644 --- a/apps/ui/components/application/ApplicationPage.tsx +++ b/apps/ui/components/application/ApplicationPage.tsx @@ -12,6 +12,7 @@ import { useRouter } from "next/router"; import Head from "./Head"; import Stepper, { StepperProps } from "./Stepper"; import NotesWhenApplying from "@/components/application/NotesWhenApplying"; +import { getApplicationPath } from "@/modules/urls"; const StyledContainer = styled(Container)` background-color: var(--color-white); @@ -103,15 +104,14 @@ export function ApplicationPageWrapper({ const { t } = useTranslation(); const router = useRouter(); - const pages = ["page1", "page2", "page3", "preview"]; + const pages = ["page1", "page2", "page3", "preview"] as const; const hideStepper = pages.filter((x) => router.asPath.match(`/${x}`)).length === 0; const steps: StepperProps = { steps: pages.map((x, i) => ({ slug: x, step: i })), completedStep: application ? calculateCompletedStep(application) : 0, - // TODO use an urlbuilder - basePath: `/application/${application?.pk ?? 0}`, + basePath: getApplicationPath(application?.pk), isFormDirty: isDirty ?? false, }; diff --git a/apps/ui/components/application/Buttons.tsx b/apps/ui/components/application/Buttons.tsx deleted file mode 100644 index f39678103b..0000000000 --- a/apps/ui/components/application/Buttons.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import { useTranslation } from "next-i18next"; -import { IconArrowRight } from "hds-react"; -import { useRouter } from "next/router"; -import { ButtonContainer } from "../common/common"; -import { MediumButton } from "../../styles/util"; - -type Props = { - applicationId: number; -}; - -const Buttons = ({ applicationId }: Props): JSX.Element => { - const { t } = useTranslation(); - - const router = useRouter(); - return ( - - router.push(`/application/${applicationId}/page2`)} - > - {t("common:prev")} - - } - type="submit" - > - {t("common:next")} - - - ); -}; - -export default Buttons; diff --git a/apps/ui/modules/urls.ts b/apps/ui/modules/urls.ts index ef21139a2a..f336983694 100644 --- a/apps/ui/modules/urls.ts +++ b/apps/ui/modules/urls.ts @@ -45,7 +45,13 @@ export function getSingleSearchPath(params?: URLSearchParams): string { return base; } -type ApplicationPages = "page1" | "page2" | "page3" | "view" | "preview"; +type ApplicationPages = + | "page1" + | "page2" + | "page3" + | "view" + | "preview" + | "sent"; export function getApplicationPath( pk: Maybe | undefined, page?: ApplicationPages | undefined diff --git a/apps/ui/pages/applications/[id]/page3.tsx b/apps/ui/pages/applications/[id]/page3.tsx index 00f4259ba1..90ad86da2e 100644 --- a/apps/ui/pages/applications/[id]/page3.tsx +++ b/apps/ui/pages/applications/[id]/page3.tsx @@ -18,7 +18,6 @@ import { IndividualForm } from "@/components/application/IndividualForm"; import { OrganisationForm } from "@/components/application/OrganisationForm"; import { ApplicantTypeSelector } from "@/components/application/ApplicantTypeSelector"; import { useOptions } from "@/hooks/useOptions"; -import Buttons from "@/components/application/Buttons"; import { convertAddress, convertOrganisation, @@ -30,16 +29,41 @@ import { } from "@/components/application/Form"; import { ApplicationPageWrapper } from "@/components/application/ApplicationPage"; import { useApplicationUpdate } from "@/hooks/useApplicationUpdate"; -import { CenterSpinner } from "@/components/common/common"; +import { CenterSpinner, ButtonContainer } from "@/components/common/common"; import { getCommonServerSideProps } from "@/modules/serverUtils"; import { base64encode } from "common/src/helpers"; import { errorToast } from "common/src/common/toast"; +import { MediumButton } from "@/styles/util"; +import { getApplicationPath } from "@/modules/urls"; +import { IconArrowRight } from "hds-react"; const Form = styled.form` margin-bottom: var(--spacing-layout-l); padding-bottom: var(--spacing-l); `; +function Buttons({ applicationPk }: { applicationPk: number }): JSX.Element { + const { t } = useTranslation(); + const router = useRouter(); + + const onPrev = () => router.push(getApplicationPath(applicationPk, "page2")); + + return ( + + + {t("common:prev")} + + } + type="submit" + > + {t("common:next")} + + + ); +} + // Filter out any empty strings from the object (otherwise the mutation fails) function transformPerson(person?: PersonFormValues) { return { @@ -187,7 +211,8 @@ function Page3Wrapped(props: Props): JSX.Element | null { } }, [application, reset]); - const [update, { error }] = useApplicationUpdate(); + const { t } = useTranslation(); + const [update] = useApplicationUpdate(); const handleSave = async (values: ApplicationFormPage3Values) => { // There should not be a situation where we are saving on this page without an application @@ -198,51 +223,33 @@ function Page3Wrapped(props: Props): JSX.Element | null { console.error("application pk is 0"); return 0; } - - const input = transformApplication(values); - const pk = await update(input); - return pk; + return update(transformApplication(values)); }; const onSubmit = async (values: ApplicationFormPage3Values) => { - const pk = await handleSave(values); - if (pk === 0) { - return; + try { + const pk = await handleSave(values); + if (pk === 0) { + return; + } + router.push(getApplicationPath(pk, "preview")); + } catch (e) { + errorToast({ text: t("common:error.dataError") }); } - const prefix = `/application/${pk}`; - const target = `${prefix}/preview`; - router.push(target); }; - const { t } = useTranslation(); - const dataErrorMessage = t("common:error.dataError"); - useEffect(() => { - if (error != null) { - errorToast({ - text: dataErrorMessage, - }); - } - }, [error, t, dataErrorMessage]); - if (id == null) { return ; } - if (isLoading) { - return ; - } if (queryError != null) { // eslint-disable-next-line no-console console.error(queryError); - // TODO should be wrapped in layout and have an option to retry the query - // probably better to show error page over a toast - // return ; return ; } - if (error != null) { - // eslint-disable-next-line no-console - console.error(error); + if (isLoading && application == null && applicationRound == null) { + return ; } // TODO these are 404 @@ -262,7 +269,7 @@ function Page3Wrapped(props: Props): JSX.Element | null { - {application.pk && } + {application.pk && } diff --git a/apps/ui/pages/applications/[id]/preview.tsx b/apps/ui/pages/applications/[id]/preview.tsx index 1999e24e5e..4ece9c4bde 100644 --- a/apps/ui/pages/applications/[id]/preview.tsx +++ b/apps/ui/pages/applications/[id]/preview.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { useTranslation } from "next-i18next"; import { useRouter } from "next/router"; import { @@ -7,7 +7,7 @@ import { } from "@gql/gql-types"; import type { GetServerSidePropsContext } from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import Error from "next/error"; +import { default as ErrorComponent } from "next/error"; import { MediumButton } from "@/styles/util"; import { ButtonContainer, CenterSpinner } from "@/components/common/common"; import { ViewInner } from "@/components/application/ViewInner"; @@ -19,6 +19,8 @@ import { } from "@/modules/serverUtils"; import { base64encode } from "common/src/helpers"; import { errorToast } from "common/src/common/toast"; +import { getApplicationPath } from "@/modules/urls"; +import { ButtonLikeLink } from "@/components/common/ButtonLikeLink"; type Props = Awaited>["props"]; type PropsNarrowed = Exclude; @@ -45,70 +47,46 @@ function Preview(props: PropsNarrowed): JSX.Element { const { t } = useTranslation(); - const [send, { error: mutationError, loading: isMutationLoading }] = - useSendApplicationMutation(); + const [send, { loading: isMutationLoading }] = useSendApplicationMutation(); const onSubmit = async (evt: React.FormEvent) => { evt.preventDefault(); if (!acceptTermsOfUse) { return; } - if (!pk) { - // eslint-disable-next-line no-console - console.error("no pk in values"); - return; - } - const { data: mutData, errors } = await send({ - variables: { - input: { - pk, + try { + const { data: mutData } = await send({ + variables: { + input: { + pk, + }, }, - }, - }); - if (errors) { - // eslint-disable-next-line no-console - console.error("error sending application", errors); - // TODO show error - return; - } + }); - const { pk: resPk } = mutData?.sendApplication ?? {}; + const { pk: resPk } = mutData?.sendApplication ?? {}; + if (resPk == null) { + throw new Error("no pk in response"); + } - if (resPk != null) { - // TODO use an urlbuilder - const prefix = `/application/${resPk}`; - const target = `${prefix}/sent`; - router.push(target); + router.push(getApplicationPath(resPk, "sent")); + } catch (e) { + errorToast({ text: t("common:error.mutationError") }); } - // TODO error }; - useEffect(() => { - mutationError && - errorToast({ - text: t("common:error.mutationError"), - }); - }, [mutationError, t]); - - if (pk == null) { - return ; - } if (error) { // eslint-disable-next-line no-console console.error(error); - return ; + return ; } if (isLoading) { return ; } if (application == null) { - return ; + return ; } - // TODO use an urlbuilder - const handleBack = () => router.push(`/application/${application.pk}/page3`); - return ( - + {t("common:prev")} - +
Date: Mon, 7 Oct 2024 14:38:08 +0300 Subject: [PATCH 07/16] refactor: remove koros from applications --- .../application/ApplicationPage.tsx | 50 +++++++------------ apps/ui/components/application/Head.tsx | 42 ++++------------ .../application/NotesWhenApplying.tsx | 4 +- .../components/common/BreadcrumbWrapper.tsx | 3 ++ apps/ui/components/common/PageWrapper.tsx | 8 +-- apps/ui/pages/applications/[id]/sent.tsx | 28 +++++------ apps/ui/pages/applications/[id]/view.tsx | 36 ++++++++++--- .../pages/recurring/[id]/criteria/index.tsx | 30 ++++------- apps/ui/pages/reservations/[id]/index.tsx | 1 - apps/ui/public/locales/en/application.json | 4 +- apps/ui/public/locales/fi/application.json | 4 +- apps/ui/public/locales/sv/application.json | 4 +- packages/common/src/breadcrumb/Breadcrumb.tsx | 16 ++++-- packages/common/src/layout/Container.tsx | 2 +- 14 files changed, 110 insertions(+), 122 deletions(-) diff --git a/apps/ui/components/application/ApplicationPage.tsx b/apps/ui/components/application/ApplicationPage.tsx index 3213bf41ba..c9ff16c1be 100644 --- a/apps/ui/components/application/ApplicationPage.tsx +++ b/apps/ui/components/application/ApplicationPage.tsx @@ -9,39 +9,28 @@ import { type ApplicationQuery, } from "@gql/gql-types"; import { useRouter } from "next/router"; -import Head from "./Head"; +import { Head } from "./Head"; import Stepper, { StepperProps } from "./Stepper"; import NotesWhenApplying from "@/components/application/NotesWhenApplying"; import { getApplicationPath } from "@/modules/urls"; -const StyledContainer = styled(Container)` - background-color: var(--color-white); -`; - const InnerContainer = styled.div<{ $hideStepper: boolean }>` display: grid; gap: 1em; ${({ $hideStepper }) => $hideStepper - ? `grid-template-columns: 6em 1fr;` + ? `grid-template-columns: 1fr;` : `grid-template-columns: 18em 1fr;`} @media (max-width: ${breakpoints.l}) { grid-template-columns: 1fr; - gap: 0; - } -`; - -const Main = styled.div` - margin-top: var(--spacing-s); - - @media (max-width: ${breakpoints.s}) { - width: calc(100vw - 3 * var(--spacing-xs)); } `; const NotesWrapper = styled.div` - margin-bottom: var(--spacing-m); + display: flex; + flex-direction: column; + gap: var(--spacing-m); `; // TODO this should have more complete checks (but we are thinking of splitting the form anyway) @@ -116,23 +105,22 @@ export function ApplicationPageWrapper({ }; return ( - <> + {headContent || overrideText || t(`${translationKeyPrefix}.text`)} - - - {hideStepper ?
: } -
- - - - {children} -
- - - + + {hideStepper ? null : } + {/* TODO preview / view should not maybe display these notes */} + +
+ +
+ {children} +
+
+ ); } diff --git a/apps/ui/components/application/Head.tsx b/apps/ui/components/application/Head.tsx index 2fdf97d4e9..1364ca576d 100644 --- a/apps/ui/components/application/Head.tsx +++ b/apps/ui/components/application/Head.tsx @@ -1,9 +1,7 @@ import React from "react"; -import { useRouter } from "next/router"; import styled from "styled-components"; -import { H2 } from "common/src/common/typography"; +import { fontMedium, H2 } from "common/src/common/typography"; import BreadcrumbWrapper from "../common/BreadcrumbWrapper"; -import KorosDefault from "../common/KorosDefault"; type HeadProps = { heading: string; @@ -13,42 +11,24 @@ type HeadProps = { const Heading = styled(H2).attrs({ as: "h1" })``; -const Container = styled.div<{ $white: boolean }>` - background-color: ${({ $white }) => - $white ? "var(--color-white)" : "var(--tilavaraus-hero-background-color)"}; - color: ${({ $white }) => - $white ? "var(--color-black)" : "var(--color-white)"}; -`; - const Content = styled.div` - padding: var(--spacing-l) var(--spacing-m) var(--spacing-layout-l); max-width: var(--container-width-xl); - margin: 0 auto; + margin: var(--spacing-layout-m) 0 auto; font-size: var(--fontsize-heading-xs); - font-weight: 500; -`; - -const StyledKoros = styled(KorosDefault)` - margin-top: var(--spacing-layout-m); + ${fontMedium} `; -const Head = ({ - children, - heading, - noKoros = false, -}: HeadProps): JSX.Element => { - const router = useRouter(); - +export function Head({ children, heading }: HeadProps): JSX.Element { return ( - - + <> + {heading} {children || null} - {!noKoros && } - + ); -}; - -export default Head; +} diff --git a/apps/ui/components/application/NotesWhenApplying.tsx b/apps/ui/components/application/NotesWhenApplying.tsx index b0c668a932..7dee88f962 100644 --- a/apps/ui/components/application/NotesWhenApplying.tsx +++ b/apps/ui/components/application/NotesWhenApplying.tsx @@ -34,7 +34,7 @@ type NotesWhenApplyingProps = { > | null; }; -const NotesWhenApplying = ({ applicationRound }: NotesWhenApplyingProps) => { +function NotesWhenApplying({ applicationRound }: NotesWhenApplyingProps) { const { t, i18n } = useTranslation(); if (!applicationRound) { @@ -58,6 +58,6 @@ const NotesWhenApplying = ({ applicationRound }: NotesWhenApplyingProps) => { ); -}; +} export default NotesWhenApplying; diff --git a/apps/ui/components/common/BreadcrumbWrapper.tsx b/apps/ui/components/common/BreadcrumbWrapper.tsx index 8a769b621c..731cbf24ea 100644 --- a/apps/ui/components/common/BreadcrumbWrapper.tsx +++ b/apps/ui/components/common/BreadcrumbWrapper.tsx @@ -14,6 +14,7 @@ type Alias = { type Props = { route: Array; + disablePadding?: boolean; aliases?: Alias[]; className?: string; }; @@ -31,6 +32,7 @@ const LinkWrapper = (props: LinkProps & { children?: React.ReactNode }) => ( const BreadcrumbWrapper = ({ route, aliases, + disablePadding, className, }: Props): JSX.Element => { const { t } = useTranslation(); @@ -52,6 +54,7 @@ const BreadcrumbWrapper = ({ ` +const Main = styled.main` font-size: var(--fontsize-body-m); flex: 1 0 auto; margin-bottom: -14px; - background: ${({ $bgColor }) => $bgColor || "var(--color-white)"}; `; function PageWrapper({ @@ -28,7 +27,6 @@ function PageWrapper({ profileLink, feedbackUrl, children, - overrideBackgroundColor, version, }: Props): JSX.Element { return ( @@ -43,9 +41,7 @@ function PageWrapper({ target={BannerNotificationTarget.User} /> -
- {children} -
+
{children}