1- import React , { useEffect , useMemo , useState } from "react" ;
1+ import React , { useEffect , useState } from "react" ;
22import styled from "styled-components" ;
33import router from "next/router" ;
44import { Controller , useForm } from "react-hook-form" ;
@@ -7,22 +7,29 @@ import { useTranslation } from "next-i18next";
77import { OptionType } from "common/types/common" ;
88import { breakpoints } from "common/src/common/style" ;
99import NotificationBox from "common/src/common/NotificationBox" ;
10- import { fontMedium , H2 } from "common/src/common/typography" ;
10+ import { fontMedium } from "common/src/common/typography" ;
1111import {
1212 useCancelReservationMutation ,
1313 type ReservationQuery ,
1414 type ReservationCancelReasonsQuery ,
1515} from "@gql/gql-types" ;
16- import { Container as CommonContainer } from "common" ;
1716import { IconButton , ShowAllContainer } from "common/src/components" ;
1817import Sanitize from "../common/Sanitize" ;
19- import { JustForDesktop , JustForMobile } from "@/modules/style/layout" ;
2018import { getSelectedOption , getTranslation } from "@/modules/util" ;
21- import { CenterSpinner } from "../common/common" ;
2219import { BlackButton , MediumButton , Toast } from "@/styles/util" ;
2320import { ReservationInfoCard } from "./ReservationInfoCard" ;
2421import { Paragraph } from "./styles" ;
2522import { signOut } from "@/hooks/auth" ;
23+ import {
24+ BylineSection ,
25+ Heading ,
26+ HeadingSection ,
27+ ReservationPageWrapper ,
28+ } from "../reservations/styles" ;
29+ import {
30+ convertLanguageCode ,
31+ getTranslationSafe ,
32+ } from "common/src/common/util" ;
2633
2734type CancelReasonsQ = NonNullable <
2835 ReservationCancelReasonsQuery [ "reservationCancelReasons" ]
@@ -35,29 +42,9 @@ type NodeT = ReservationQuery["reservation"];
3542type Props = {
3643 apiBaseUrl : string ;
3744 reasons : CancelReasonsNode [ ] ;
38- reservation : NodeT ;
45+ reservation : NonNullable < NodeT > ;
3946} ;
4047
41- const Spinner = styled ( CenterSpinner ) `
42- margin: var(--spacing-layout-xl) auto;
43- ` ;
44-
45- const Wrapper = styled . div `
46- background-color: var(--color-white);
47- ` ;
48-
49- const Container = styled ( CommonContainer ) `
50- @media (min-width: ${ breakpoints . m } ) {
51- margin-bottom: var(--spacing-layout-l);
52- }
53- ` ;
54-
55- const Title = styled ( H2 ) . attrs ( { as : "h1" } ) `` ;
56-
57- const Heading = styled . div `
58- font-size: var(--fontsize-body-l);
59- ` ;
60-
6148const TermsContainer = styled ( ShowAllContainer ) `
6249 .ShowAllContainer__ToggleButton {
6350 color: var(--color-bus);
@@ -80,32 +67,13 @@ const Actions = styled.div`
8067 }
8168` ;
8269
83- const Content = styled . div `
84- font-size: var(--fontsize-body-l);
85- ` ;
86-
87- const ContentContainer = styled . div `
88- margin-bottom: var(--spacing-xl);
89- white-space: pre-line;
90-
91- div[role="heading"] {
92- font-size: var(--fontsize-heading-s);
93- }
94- ` ;
95-
96- const Columns = styled . div `
97- grid-template-columns: 1fr;
98- display: grid;
99- align-items: flex-start;
100- gap: var(--spacing-l);
101-
102- @media (min-width: ${ breakpoints . m } ) {
103- & > div:nth-of-type(1) {
104- order: 2;
105- }
70+ const ContentSection = styled . div `
71+ grid-column: 1 / -1;
72+ grid-row: 3 / -1;
10673
107- margin-top: var(--spacing-xl);
108- grid-template-columns: 1fr 378px;
74+ @media (min-width: ${ breakpoints . l } ) {
75+ grid-column: 1 / span 4;
76+ grid-row: 2 / -1;
10977 }
11078` ;
11179
@@ -129,18 +97,13 @@ const ReturnLinkContainer = styled.div`
12997 display: flex;
13098 flex-direction: column;
13199 align-items: flex-start;
100+ margin-top: var(--spacing-3-xl);
132101` ;
133102
134- function ReturnLinkList ( {
135- apiBaseUrl,
136- style,
137- } : {
138- apiBaseUrl : string ;
139- style ?: React . CSSProperties ;
140- } ) : JSX . Element {
103+ function ReturnLinkList ( { apiBaseUrl } : { apiBaseUrl : string } ) : JSX . Element {
141104 const { t } = useTranslation ( ) ;
142105 return (
143- < ReturnLinkContainer style = { style } >
106+ < ReturnLinkContainer >
144107 < IconButton
145108 href = "/reservations"
146109 label = { t ( "reservations:gotoReservations" ) }
@@ -161,16 +124,17 @@ function ReturnLinkList({
161124}
162125
163126// TODO there is also pages/reservation/cancel.tsx (what is that?)
164- function ReservationCancellation ( props : Props ) : JSX . Element {
165- const { t } = useTranslation ( ) ;
127+ export function ReservationCancellation ( props : Props ) : JSX . Element {
128+ const { t, i18n } = useTranslation ( ) ;
166129 const { apiBaseUrl } = props ;
167130
168131 const [ errorMsg , setErrorMsg ] = useState < string | null > ( null ) ;
169132 const [ formState , setFormState ] = useState < "unsent" | "sent" > ( "unsent" ) ;
170133
134+ const lang = convertLanguageCode ( i18n . language ) ;
171135 const reasons = props . reasons . map ( ( node ) => ( {
172- label : getTranslation ( node , "reason" ) ,
173- value : node ?. pk != null ? node ?. pk : "" ,
136+ label : getTranslationSafe ( node , "reason" , lang ) ,
137+ value : node ?. pk ?? 0 ,
174138 } ) ) ;
175139
176140 const [ cancelReservation , { loading } ] = useCancelReservationMutation ( ) ;
@@ -185,22 +149,14 @@ function ReservationCancellation(props: Props): JSX.Element {
185149
186150 const { reservation } = props ;
187151
188- const bylineContent = useMemo ( ( ) => {
189- return (
190- reservation && (
191- < ReservationInfoCard reservation = { reservation } type = "confirmed" />
192- )
193- ) ;
194- } , [ reservation ] ) ;
195-
196- if ( ! reservation ) {
197- return < Spinner /> ;
198- }
199-
200152 const reservationUnit = reservation ?. reservationUnit ?. [ 0 ] ?? null ;
201153 const instructions = reservationUnit
202- ? getTranslation ( reservationUnit , "reservationCancelledInstructions" )
203- : undefined ;
154+ ? getTranslationSafe (
155+ reservationUnit ,
156+ "reservationCancelledInstructions" ,
157+ lang
158+ )
159+ : null ;
204160
205161 const onSubmit = ( formData : { reason ?: number ; description ?: string } ) => {
206162 if ( ! reservation . pk || ! formData . reason ) {
@@ -225,115 +181,97 @@ function ReservationCancellation(props: Props): JSX.Element {
225181 }
226182 } ;
227183
184+ const title =
185+ formState === "unsent"
186+ ? t ( "reservations:cancelReservation" )
187+ : t ( "reservations:reservationCancelledTitle" ) ;
188+ const ingress =
189+ formState === "unsent"
190+ ? t ( "reservations:cancelReservationBody" )
191+ : t ( "reservations:reservationCancelledBody" ) ;
192+
193+ const cancellationTerms =
194+ reservationUnit ?. cancellationTerms != null
195+ ? getTranslation ( reservationUnit ?. cancellationTerms , "text" )
196+ : null ;
197+
228198 return (
229- < Wrapper >
230- < Container >
231- < Columns >
232- < div >
233- < JustForDesktop > { bylineContent } </ JustForDesktop >
234- </ div >
235- < div >
236- < Heading >
237- { formState === "unsent" ? (
238- < >
239- < Title > { t ( "reservations:cancelReservation" ) } </ Title >
240- < JustForMobile > { bylineContent } </ JustForMobile >
241- < p > { t ( "reservations:cancelReservationBody" ) } </ p >
242- </ >
243- ) : (
244- < >
245- < Title > { t ( "reservations:reservationCancelledTitle" ) } </ Title >
246- < JustForMobile > { bylineContent } </ JustForMobile >
247- < p > { t ( "reservations:reservationCancelledBody" ) } </ p >
248- </ >
249- ) }
250- </ Heading >
251- < Content >
252- < ContentContainer >
253- { formState === "unsent" ? (
254- < >
255- < p > { t ( "reservations:cancelInfoBody" ) } </ p >
256- < TermsContainer
257- showAllLabel = { t ( "reservations:showCancellationTerms" ) }
258- showLessLabel = { t ( "reservations:hideCancellationTerms" ) }
259- maximumNumber = { 0 }
260- >
261- { reservationUnit ?. cancellationTerms != null && (
262- < NotificationBox
263- heading = { t ( "reservationUnit:cancellationTerms" ) }
264- body = {
265- < Sanitize
266- html = { getTranslation (
267- reservationUnit ?. cancellationTerms ,
268- "text"
269- ) }
270- />
271- }
272- />
273- ) }
274- </ TermsContainer >
275- < Form onSubmit = { handleSubmit ( onSubmit ) } >
276- < Controller
277- name = "reason"
278- control = { control }
279- render = { ( ) => (
280- < StyledSelect
281- id = "reservation__button--cancel-reason"
282- label = { t ( "reservations:cancelReason" ) }
283- onChange = { ( val : OptionType ) => {
284- setValue ( "reason" , val . value ) ;
285- } }
286- options = { [ ...reasons ] }
287- placeholder = { t ( "common:select" ) }
288- value = { getSelectedOption (
289- getValues ( "reason" ) ,
290- reasons
291- ) }
292- required
293- />
294- ) }
295- />
296- < Actions >
297- < BlackButton
298- variant = "secondary"
299- iconLeft = { < IconCross aria-hidden /> }
300- onClick = { ( ) => router . back ( ) }
301- data-testid = "reservation-cancel__button--back"
302- >
303- { t ( "reservations:cancelReservationCancellation" ) }
304- </ BlackButton >
305- < MediumButton
306- variant = "primary"
307- type = "submit"
308- disabled = { ! watch ( "reason" ) }
309- data-testid = "reservation-cancel__button--cancel"
310- isLoading = { loading }
311- >
312- { t ( "reservations:cancelReservation" ) }
313- </ MediumButton >
314- </ Actions >
315- </ Form >
316- </ >
317- ) : (
318- < >
319- { formState === "sent" && instructions && (
320- < Paragraph style = { { margin : "var(--spacing-xl) 0" } } >
321- { instructions }
322- </ Paragraph >
323- ) }
324- < ReturnLinkList
325- apiBaseUrl = { apiBaseUrl }
326- style = { {
327- marginTop : "var(--spacing-3-xl)" ,
199+ < >
200+ < ReservationPageWrapper >
201+ < HeadingSection >
202+ < Heading > { title } </ Heading >
203+ < p > { ingress } </ p >
204+ </ HeadingSection >
205+ < BylineSection >
206+ < ReservationInfoCard reservation = { reservation } type = "confirmed" />
207+ </ BylineSection >
208+ < ContentSection >
209+ { formState === "unsent" ? (
210+ < >
211+ < p > { t ( "reservations:cancelInfoBody" ) } </ p >
212+ < TermsContainer
213+ showAllLabel = { t ( "reservations:showCancellationTerms" ) }
214+ showLessLabel = { t ( "reservations:hideCancellationTerms" ) }
215+ maximumNumber = { 0 }
216+ >
217+ { cancellationTerms != null && (
218+ < NotificationBox
219+ heading = { t ( "reservationUnit:cancellationTerms" ) }
220+ body = { < Sanitize html = { cancellationTerms } /> }
221+ />
222+ ) }
223+ </ TermsContainer >
224+ < Form onSubmit = { handleSubmit ( onSubmit ) } >
225+ < Controller
226+ name = "reason"
227+ control = { control }
228+ render = { ( ) => (
229+ < StyledSelect
230+ id = "reservation__button--cancel-reason"
231+ label = { t ( "reservations:cancelReason" ) }
232+ onChange = { ( val : OptionType ) => {
233+ setValue ( "reason" , val . value ) ;
328234 } }
235+ options = { [ ...reasons ] }
236+ placeholder = { t ( "common:select" ) }
237+ value = { getSelectedOption ( getValues ( "reason" ) , reasons ) }
238+ required
329239 />
330- </ >
331- ) }
332- </ ContentContainer >
333- </ Content >
334- </ div >
335- </ Columns >
336- </ Container >
240+ ) }
241+ />
242+ < Actions >
243+ < BlackButton
244+ variant = "secondary"
245+ iconLeft = { < IconCross aria-hidden /> }
246+ onClick = { ( ) => router . back ( ) }
247+ data-testid = "reservation-cancel__button--back"
248+ >
249+ { t ( "reservations:cancelReservationCancellation" ) }
250+ </ BlackButton >
251+ < MediumButton
252+ variant = "primary"
253+ type = "submit"
254+ disabled = { ! watch ( "reason" ) }
255+ data-testid = "reservation-cancel__button--cancel"
256+ isLoading = { loading }
257+ >
258+ { t ( "reservations:cancelReservation" ) }
259+ </ MediumButton >
260+ </ Actions >
261+ </ Form >
262+ </ >
263+ ) : (
264+ < >
265+ { formState === "sent" && instructions && (
266+ < Paragraph style = { { margin : "var(--spacing-xl) 0" } } >
267+ { instructions }
268+ </ Paragraph >
269+ ) }
270+ < ReturnLinkList apiBaseUrl = { apiBaseUrl } />
271+ </ >
272+ ) }
273+ </ ContentSection >
274+ </ ReservationPageWrapper >
337275 { errorMsg && (
338276 < Toast
339277 type = "error"
@@ -348,8 +286,6 @@ function ReservationCancellation(props: Props): JSX.Element {
348286 { errorMsg }
349287 </ Toast >
350288 ) }
351- </ Wrapper >
289+ </ >
352290 ) ;
353291}
354-
355- export default ReservationCancellation ;
0 commit comments