@autobusal/routes-order 1.22.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/Checkout/Checkout.tsx +24 -2
- package/Found/Route/Route.tsx +6 -6
- package/Step5/Addons/Addons.tsx +84 -4
- package/Step5/Addons/styles.ts +79 -0
- package/package.json +1 -1
- package/services.ts +46 -2
- package/types.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.23.1
|
|
4
|
+
|
|
5
|
+
**Flexible Ticket as a title, a price and an Add button, with the detail collapsed.**
|
|
6
|
+
|
|
7
|
+
A button rather than a checkbox, because this is not the same kind of choice as the notification add-ons beside it — those are a cheap tick, this one changes what the ticket *is*. The toggle and the Add button are separate controls on purpose: reading the terms must never be a way to accidentally buy them.
|
|
8
|
+
|
|
9
|
+
One line stays **outside** the collapse — "without it, this ticket cannot be cancelled or changed". Everything else is detail somebody can choose to read, but that is the material fact, and hiding what a buyer gets for nothing behind a toggle is the shape of a dark pattern rather than a tidy layout.
|
|
10
|
+
|
|
11
|
+
## 1.23.0
|
|
12
|
+
|
|
13
|
+
**Flexible Ticket at checkout.**
|
|
14
|
+
|
|
15
|
+
- New `useGetFlex(departurePriceId, returnPriceId, adults, children)`. Its own request rather than a field on the search result, because the answer depends on **both legs at once**: an operator who will not amend one half removes the add-on from the whole booking, and the price is computed on the combined fare.
|
|
16
|
+
- `Addons` renders the option below passenger details, where it already sat. It shows the **default first** — "without it, this ticket cannot be cancelled or changed" — then only the capabilities this booking actually includes, then that the fee itself is not refunded, then that statutory rights when the *operator* cancels are untouched. Nothing renders at all when the offer is unavailable: a checkbox that unlocks nothing is worse than no checkbox.
|
|
17
|
+
- `AddonsSelection` gains `flex`; the order payload gains `addon_flex`.
|
|
18
|
+
- `Found/Route` reads `routes_order.step2.route.information.*` after the `policies` rename.
|
|
19
|
+
|
|
20
|
+
Copy is **Flexible Ticket** / **Biletë Fleksibël**, never insurance — the distinction decides how the line is taxed and whether selling it needs a licence.
|
|
21
|
+
|
|
3
22
|
All notable changes to this package are documented here. This project follows
|
|
4
23
|
[Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
|
5
24
|
|
package/Checkout/Checkout.tsx
CHANGED
|
@@ -24,7 +24,7 @@ import { CouponData } from '@autobusal/providers/types/orders';
|
|
|
24
24
|
import { TotalData } from '../types';
|
|
25
25
|
import { useUserStore } from '@autobusal/providers/stores/user';
|
|
26
26
|
import { useGetSettings } from '@autobusal/providers/services';
|
|
27
|
-
import { usePostOrder } from '../services';
|
|
27
|
+
import { usePostOrder, useGetFlex } from '../services';
|
|
28
28
|
|
|
29
29
|
interface Props {
|
|
30
30
|
values?: PersonData
|
|
@@ -75,6 +75,7 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
75
75
|
const [ addonWhatsapp, setAddonWhatsapp ] = useState<boolean>(false);
|
|
76
76
|
const [ addonWhatsappNumber, setAddonWhatsappNumber ] = useState<string>('');
|
|
77
77
|
const [ addonTelegram, setAddonTelegram ] = useState<boolean>(false);
|
|
78
|
+
const [ addonFlex, setAddonFlex ] = useState<boolean>(false);
|
|
78
79
|
|
|
79
80
|
const { data: UserData } = useUserStore();
|
|
80
81
|
const { data: SettingsData } = useGetSettings();
|
|
@@ -93,6 +94,19 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
93
94
|
|
|
94
95
|
const currency = total.display.split(' ')[1] ?? '';
|
|
95
96
|
|
|
97
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
98
|
+
// Flexible Ticket, for the routes actually chosen. Asked for BOTH legs at
|
|
99
|
+
// once: the price is computed on the combined fare and an operator who
|
|
100
|
+
// will not amend one half removes the offer from the whole booking, so a
|
|
101
|
+
// per-leg question could not be answered honestly.
|
|
102
|
+
const { data: FlexData } = useGetFlex(
|
|
103
|
+
step2.price.id as number,
|
|
104
|
+
step3?.price.id as (number | undefined),
|
|
105
|
+
step1.adults ?? 1,
|
|
106
|
+
step1.children ?? 0,
|
|
107
|
+
step1.babies ?? 0
|
|
108
|
+
);
|
|
109
|
+
|
|
96
110
|
const addonItems = [
|
|
97
111
|
addonWhatsapp && SettingsData?.addons?.whatsapp.available ? {
|
|
98
112
|
label: t('routes_order.step5.addons.whatsapp.label'),
|
|
@@ -101,6 +115,10 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
101
115
|
addonTelegram && SettingsData?.addons?.telegram.available ? {
|
|
102
116
|
label: t('routes_order.step5.addons.telegram.label'),
|
|
103
117
|
price: SettingsData.addons.telegram.price
|
|
118
|
+
} : null,
|
|
119
|
+
addonFlex && FlexData?.available && FlexData.price !== undefined ? {
|
|
120
|
+
label: t('routes_order.step5.flex.title'),
|
|
121
|
+
price: FlexData.price
|
|
104
122
|
} : null
|
|
105
123
|
].filter((item): item is { label: string, price: number } => item !== null);
|
|
106
124
|
|
|
@@ -114,7 +132,8 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
114
132
|
const { mutate: OrderSend, isPending } = usePostOrder(step1, step2, step3, coupon, action, {
|
|
115
133
|
whatsapp: addonWhatsapp,
|
|
116
134
|
whatsappNumber: addonWhatsappNumber,
|
|
117
|
-
telegram: addonTelegram
|
|
135
|
+
telegram: addonTelegram,
|
|
136
|
+
flex: addonFlex
|
|
118
137
|
});
|
|
119
138
|
|
|
120
139
|
const onApplyCoupon = (data: CouponData): void => {
|
|
@@ -215,14 +234,17 @@ const Checkout = ({ values, step1, step2, step3, isPartner, t, onStore, onBack }
|
|
|
215
234
|
|
|
216
235
|
<Addons
|
|
217
236
|
data={ SettingsData?.addons }
|
|
237
|
+
flexOffer={ FlexData }
|
|
218
238
|
currency={ currency }
|
|
219
239
|
whatsapp={ addonWhatsapp }
|
|
220
240
|
whatsappNumber={ addonWhatsappNumber }
|
|
221
241
|
telegram={ addonTelegram }
|
|
242
|
+
flex={ addonFlex }
|
|
222
243
|
t={ t }
|
|
223
244
|
onChangeWhatsapp={ setAddonWhatsapp }
|
|
224
245
|
onChangeWhatsappNumber={ setAddonWhatsappNumber }
|
|
225
246
|
onChangeTelegram={ setAddonTelegram }
|
|
247
|
+
onChangeFlex={ setAddonFlex }
|
|
226
248
|
/>
|
|
227
249
|
|
|
228
250
|
{ UserData === undefined && (
|
package/Found/Route/Route.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { HiOutlineArrowNarrowRight, HiOutlineDocumentText, HiChevronDown, HiChev
|
|
|
4
4
|
import { BiHourglass } from 'react-icons/bi';
|
|
5
5
|
import { RiBus2Line } from 'react-icons/ri';
|
|
6
6
|
import { formatDuration, splitPrice } from '../refine';
|
|
7
|
-
import { Button, RouteFeature,
|
|
7
|
+
import { Button, RouteFeature, Information, Rating } from '@autobusal/common';
|
|
8
8
|
import { Container, ButtonDetails, SpecialOffer, FavoriteStop, Trip, Company, Location, Stop, Iso, Time, Price, Amount, Decimals, Currency, PriceNotice, LinkCompany, Details, Detail, ButtonPolicy, TitleDetail, LinkRoute, FeaturesItems, Leaves } from './styles';
|
|
9
9
|
import { FoundData } from '@autobusal/providers/types/routes';
|
|
10
10
|
|
|
@@ -25,7 +25,7 @@ interface Props {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Element => {
|
|
28
|
-
const [
|
|
28
|
+
const [ information, setInformation ] = useState<boolean>(false);
|
|
29
29
|
|
|
30
30
|
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
31
31
|
// Closed to begin with. Amenities, route code, transit and policies are
|
|
@@ -203,17 +203,17 @@ const Route = ({ data, type, passengers, t, onSave, children }: Props): JSX.Elem
|
|
|
203
203
|
|
|
204
204
|
{ !data.external && (
|
|
205
205
|
<Detail>
|
|
206
|
-
<TitleDetail>{ t('routes_order.step2.route.
|
|
206
|
+
<TitleDetail>{ t('routes_order.step2.route.information.title') }</TitleDetail>
|
|
207
207
|
|
|
208
|
-
<ButtonPolicy type="button" onClick={ () =>
|
|
208
|
+
<ButtonPolicy type="button" onClick={ () => setInformation(true) }>
|
|
209
209
|
<HiOutlineDocumentText />
|
|
210
|
-
{ t('routes_order.step2.route.
|
|
210
|
+
{ t('routes_order.step2.route.information.view') }
|
|
211
211
|
</ButtonPolicy>
|
|
212
212
|
</Detail>
|
|
213
213
|
) }
|
|
214
214
|
</Details>
|
|
215
215
|
|
|
216
|
-
{
|
|
216
|
+
{ information && typeof data.id === 'number' && <Information id={ data.id } t={ t } onClose={ () => setInformation(false) } /> }
|
|
217
217
|
</Container>
|
|
218
218
|
);
|
|
219
219
|
};
|
package/Step5/Addons/Addons.tsx
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { TFunction } from 'i18next';
|
|
2
|
-
import {
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { RiWhatsappLine, RiTelegramLine, RiCalendarCheckLine, RiArrowUpSLine, RiArrowDownSLine } from 'react-icons/ri';
|
|
4
|
+
import { FlexOffer } from '@autobusal/providers/types/routes';
|
|
3
5
|
import { SubTitle } from '../../styles';
|
|
4
|
-
import { Container, Option, Notice } from './styles';
|
|
6
|
+
import { Container, Option, Notice, Terms, Term, FlexHeader, FlexToggle, FlexPrice, FlexAdd, FlexDetails } from './styles';
|
|
5
7
|
|
|
6
8
|
interface AddonsData {
|
|
7
9
|
whatsapp: { available: boolean, price: number }
|
|
@@ -10,14 +12,17 @@ interface AddonsData {
|
|
|
10
12
|
|
|
11
13
|
interface Props {
|
|
12
14
|
data?: AddonsData
|
|
15
|
+
flexOffer?: FlexOffer
|
|
13
16
|
currency: string
|
|
14
17
|
whatsapp: boolean
|
|
15
18
|
whatsappNumber: string
|
|
16
19
|
telegram: boolean
|
|
20
|
+
flex: boolean
|
|
17
21
|
t: TFunction<'common'>
|
|
18
22
|
onChangeWhatsapp: (value: boolean) => void
|
|
19
23
|
onChangeWhatsappNumber: (value: string) => void
|
|
20
24
|
onChangeTelegram: (value: boolean) => void
|
|
25
|
+
onChangeFlex: (value: boolean) => void
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
// Edited: Ferjolt Ozuni - Date: 2026-07-25
|
|
@@ -25,8 +30,22 @@ interface Props {
|
|
|
25
30
|
// enabled (an empty box would be confusing). The WhatsApp addon collects
|
|
26
31
|
// the number here; Telegram is a "connect after purchase" flow (a bot
|
|
27
32
|
// can't message a bare phone number cold), so it's just a checkbox.
|
|
28
|
-
const Addons = ({ data, currency, whatsapp, whatsappNumber, telegram, t, onChangeWhatsapp, onChangeWhatsappNumber, onChangeTelegram }: Props): (JSX.Element | null) => {
|
|
29
|
-
|
|
33
|
+
const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram, flex, t, onChangeWhatsapp, onChangeWhatsappNumber, onChangeTelegram, onChangeFlex }: Props): (JSX.Element | null) => {
|
|
34
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
35
|
+
// Flexible Ticket is only on offer where the operators behind the chosen
|
|
36
|
+
// routes have agreed to honour it, which is a per-booking answer the
|
|
37
|
+
// settings payload cannot give - hence the separate offer (see
|
|
38
|
+
// useGetFlex). When it is unavailable nothing renders for it at all,
|
|
39
|
+
// rather than a disabled row: a checkbox that unlocks nothing is worse
|
|
40
|
+
// than no checkbox.
|
|
41
|
+
const hasFlex = flexOffer?.available === true;
|
|
42
|
+
|
|
43
|
+
// Closed to begin with. The title, the price and the Add button are the
|
|
44
|
+
// decision; the windows and the small print are for somebody who wants to
|
|
45
|
+
// check before making it.
|
|
46
|
+
const [ open, setOpen ] = useState<boolean>(false);
|
|
47
|
+
|
|
48
|
+
if (!data || (!data.whatsapp.available && !data.telegram.available && !hasFlex)) {
|
|
30
49
|
return null;
|
|
31
50
|
}
|
|
32
51
|
|
|
@@ -80,6 +99,67 @@ const Addons = ({ data, currency, whatsapp, whatsappNumber, telegram, t, onChang
|
|
|
80
99
|
<Notice>{ t('routes_order.step5.addons.telegram.description') }</Notice>
|
|
81
100
|
</Option>
|
|
82
101
|
) }
|
|
102
|
+
|
|
103
|
+
{ hasFlex && (
|
|
104
|
+
<Option>
|
|
105
|
+
<FlexHeader>
|
|
106
|
+
<FlexToggle type="button" aria-expanded={ open } onClick={ () => setOpen(!open) }>
|
|
107
|
+
<RiCalendarCheckLine />
|
|
108
|
+
|
|
109
|
+
{ t('routes_order.step5.flex.title') }
|
|
110
|
+
|
|
111
|
+
<FlexPrice>{ flexOffer?.price_display }</FlexPrice>
|
|
112
|
+
|
|
113
|
+
{ open ? <RiArrowUpSLine /> : <RiArrowDownSLine /> }
|
|
114
|
+
</FlexToggle>
|
|
115
|
+
|
|
116
|
+
<FlexAdd type="button" $added={ flex } onClick={ () => onChangeFlex(!flex) }>
|
|
117
|
+
{ flex ? t('routes_order.step5.flex.remove') : t('routes_order.step5.flex.add') }
|
|
118
|
+
</FlexAdd>
|
|
119
|
+
</FlexHeader>
|
|
120
|
+
|
|
121
|
+
{/*
|
|
122
|
+
* Deliberately OUTSIDE the collapse. Everything else here is
|
|
123
|
+
* detail somebody can choose to read, but this one line is the
|
|
124
|
+
* material fact - a buyer is being asked to pay for a right they
|
|
125
|
+
* would otherwise not have, and hiding what they get for nothing
|
|
126
|
+
* behind a toggle is the shape of a dark pattern rather than a
|
|
127
|
+
* tidy layout.
|
|
128
|
+
*/}
|
|
129
|
+
<Notice>{ t('routes_order.step5.flex.default') }</Notice>
|
|
130
|
+
|
|
131
|
+
{ open && (
|
|
132
|
+
<FlexDetails>
|
|
133
|
+
<Terms>
|
|
134
|
+
{ flexOffer?.refund_hours != null && (
|
|
135
|
+
<Term>{ t('routes_order.step5.flex.refund', { hours: flexOffer.refund_hours }) }</Term>
|
|
136
|
+
) }
|
|
137
|
+
|
|
138
|
+
{/*
|
|
139
|
+
* Either half can be absent on its own. An operator who
|
|
140
|
+
* will move a booking but not refund it is a real case, and
|
|
141
|
+
* listing only what this booking actually includes is the
|
|
142
|
+
* difference between an offer and a misrepresentation.
|
|
143
|
+
*/}
|
|
144
|
+
{ flexOffer?.reschedule_hours != null && (
|
|
145
|
+
<Term>{ t('routes_order.step5.flex.reschedule', { hours: flexOffer.reschedule_hours }) }</Term>
|
|
146
|
+
) }
|
|
147
|
+
</Terms>
|
|
148
|
+
|
|
149
|
+
<Notice>{ t('routes_order.step5.flex.nonrefundable') }</Notice>
|
|
150
|
+
|
|
151
|
+
{/*
|
|
152
|
+
* Statutory rights when the OPERATOR cancels are untouched by
|
|
153
|
+
* this and by anything else sold here. Saying so is both
|
|
154
|
+
* accurate and reassuring; leaving it out would imply the
|
|
155
|
+
* add-on is what grants them.
|
|
156
|
+
*/}
|
|
157
|
+
<Notice>{ t('routes_order.step5.flex.carrier') }</Notice>
|
|
158
|
+
</FlexDetails>
|
|
159
|
+
) }
|
|
160
|
+
</Option>
|
|
161
|
+
) }
|
|
162
|
+
|
|
83
163
|
</Container>
|
|
84
164
|
);
|
|
85
165
|
};
|
package/Step5/Addons/styles.ts
CHANGED
|
@@ -30,3 +30,82 @@ export const Notice = styled.div`
|
|
|
30
30
|
font-size: ${ props => props.theme.size.xs };
|
|
31
31
|
color: ${ props => props.theme.font.faded };
|
|
32
32
|
`;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* What the add-on actually unlocks, as a short list.
|
|
36
|
+
*
|
|
37
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
38
|
+
* A list rather than a sentence because the two windows can differ, and
|
|
39
|
+
* because either can be absent - prose would have to be rewritten for each
|
|
40
|
+
* combination, a list simply gets shorter.
|
|
41
|
+
*/
|
|
42
|
+
export const Terms = styled.ul`
|
|
43
|
+
margin: 8px 0 10px;
|
|
44
|
+
padding: 0 0 0 18px;
|
|
45
|
+
list-style: disc;
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
export const Term = styled.li`
|
|
49
|
+
margin: 2px 0;
|
|
50
|
+
font-size: 14px;
|
|
51
|
+
line-height: 1.5;
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The Flexible Ticket row: what it is and its price on the left, the
|
|
56
|
+
* decision on the right.
|
|
57
|
+
*
|
|
58
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
59
|
+
*
|
|
60
|
+
* A button rather than a checkbox, because this is not the same kind of
|
|
61
|
+
* choice as the notification add-ons beside it - those are a cheap tick, this
|
|
62
|
+
* one changes what the ticket IS. The two halves are separate controls on
|
|
63
|
+
* purpose: reading the terms must never be a way to accidentally buy them.
|
|
64
|
+
*/
|
|
65
|
+
export const FlexHeader = styled.div`
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
justify-content: space-between;
|
|
69
|
+
gap: 10px;
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
export const FlexToggle = styled.button`
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 6px;
|
|
76
|
+
padding: 0;
|
|
77
|
+
border: 0;
|
|
78
|
+
background: none;
|
|
79
|
+
color: inherit;
|
|
80
|
+
font-family: inherit;
|
|
81
|
+
font-size: inherit;
|
|
82
|
+
font-weight: 700;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
text-align: left;
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
export const FlexPrice = styled.span`
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
export const FlexAdd = styled.button<{ $added: boolean }>`
|
|
93
|
+
flex: 0 0 auto;
|
|
94
|
+
padding: 6px 16px;
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
border: 1px solid ${ props => props.theme.primary.normal };
|
|
97
|
+
background: ${ props => props.$added ? 'transparent' : props.theme.primary.normal };
|
|
98
|
+
color: ${ props => props.$added ? props.theme.primary.normal : props.theme.primary.contrast };
|
|
99
|
+
font-family: inherit;
|
|
100
|
+
font-size: ${ props => props.theme.size.xs };
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
|
|
104
|
+
&:hover {
|
|
105
|
+
opacity: 0.85;
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
export const FlexDetails = styled.div`
|
|
110
|
+
margin-left: 22px;
|
|
111
|
+
`;
|
package/package.json
CHANGED
package/services.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMutation, UseMutationResult, useQuery, UseQueryResult } from '@tanstack/react-query';
|
|
2
2
|
import { apiClient } from '@autobusal/providers';
|
|
3
3
|
import { RoutesSearchForm } from '@autobusal/routes-search/types';
|
|
4
|
-
import { FoundData } from '@autobusal/providers/types/routes';
|
|
4
|
+
import { FoundData, FlexOffer } from '@autobusal/providers/types/routes';
|
|
5
5
|
import { CouponData } from '@autobusal/providers/types/orders';
|
|
6
6
|
import { BillingData, PersonData } from '@autobusal/providers/types/persons';
|
|
7
7
|
import { FoundDay, ActionData, SaveData, OrderedData, CouponForm, AddonsSelection, AlternativesData } from './types';
|
|
@@ -156,6 +156,10 @@ export const usePostOrder = (
|
|
|
156
156
|
order.addon_telegram = '1';
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
if (addons.flex) {
|
|
160
|
+
order.addon_flex = '1';
|
|
161
|
+
}
|
|
162
|
+
|
|
159
163
|
Object.keys(step4).forEach(index => {
|
|
160
164
|
order[index] = step4[index];
|
|
161
165
|
});
|
|
@@ -207,4 +211,44 @@ export const useGetPayments = (): UseQueryResult<ActionData> => (
|
|
|
207
211
|
))
|
|
208
212
|
)
|
|
209
213
|
})
|
|
210
|
-
);
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The Flexible Ticket offer for the routes actually chosen.
|
|
218
|
+
*
|
|
219
|
+
* Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
220
|
+
*
|
|
221
|
+
* Its own request rather than a field on the search result, because the
|
|
222
|
+
* answer depends on BOTH legs of a return trip at once: an operator who will
|
|
223
|
+
* not amend one half removes the add-on from the whole booking, and the
|
|
224
|
+
* price is computed on the combined fare. A per-route field could not say
|
|
225
|
+
* either of those things.
|
|
226
|
+
*
|
|
227
|
+
* Disabled until there is a departure price to ask about, so it never fires
|
|
228
|
+
* on a half-built booking.
|
|
229
|
+
*/
|
|
230
|
+
export const useGetFlex = (
|
|
231
|
+
departurePriceId: (number | undefined),
|
|
232
|
+
returnPriceId: (number | undefined),
|
|
233
|
+
adults: number,
|
|
234
|
+
children: number,
|
|
235
|
+
babies: number
|
|
236
|
+
): UseQueryResult<FlexOffer> => (
|
|
237
|
+
useQuery({
|
|
238
|
+
queryKey: ['flex-offer', { departurePriceId, returnPriceId, adults, children, babies }],
|
|
239
|
+
enabled: departurePriceId !== undefined,
|
|
240
|
+
queryFn: async () => (
|
|
241
|
+
await apiClient
|
|
242
|
+
.get('/api/orders/flex', {
|
|
243
|
+
params: {
|
|
244
|
+
departure_price_id: departurePriceId,
|
|
245
|
+
return_price_id: returnPriceId,
|
|
246
|
+
adults,
|
|
247
|
+
children,
|
|
248
|
+
babies
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
.then(response => response.data)
|
|
252
|
+
)
|
|
253
|
+
})
|
|
254
|
+
);
|
package/types.ts
CHANGED
|
@@ -71,4 +71,10 @@ export interface AddonsSelection {
|
|
|
71
71
|
whatsapp: boolean
|
|
72
72
|
whatsappNumber: string
|
|
73
73
|
telegram: boolean
|
|
74
|
+
|
|
75
|
+
// Edited: Ferjolt Ozuni - Date: 2026-08-03
|
|
76
|
+
// Flexible Ticket. A bare boolean, like Telegram - the window and the
|
|
77
|
+
// price are the server's to decide (Libraries\Orders\Flex), and a client
|
|
78
|
+
// that sent either would be ignored.
|
|
79
|
+
flex: boolean
|
|
74
80
|
}
|