@autobusal/routes-order 1.23.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 +8 -0
- package/Step5/Addons/Addons.tsx +57 -40
- package/Step5/Addons/styles.ts +59 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 1.23.0
|
|
4
12
|
|
|
5
13
|
**Flexible Ticket at checkout.**
|
package/Step5/Addons/Addons.tsx
CHANGED
|
@@ -1,8 +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';
|
|
3
4
|
import { FlexOffer } from '@autobusal/providers/types/routes';
|
|
4
5
|
import { SubTitle } from '../../styles';
|
|
5
|
-
import { Container, Option, Notice, Terms, Term } from './styles';
|
|
6
|
+
import { Container, Option, Notice, Terms, Term, FlexHeader, FlexToggle, FlexPrice, FlexAdd, FlexDetails } from './styles';
|
|
6
7
|
|
|
7
8
|
interface AddonsData {
|
|
8
9
|
whatsapp: { available: boolean, price: number }
|
|
@@ -39,6 +40,11 @@ const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram,
|
|
|
39
40
|
// than no checkbox.
|
|
40
41
|
const hasFlex = flexOffer?.available === true;
|
|
41
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
|
+
|
|
42
48
|
if (!data || (!data.whatsapp.available && !data.telegram.available && !hasFlex)) {
|
|
43
49
|
return null;
|
|
44
50
|
}
|
|
@@ -96,53 +102,64 @@ const Addons = ({ data, flexOffer, currency, whatsapp, whatsappNumber, telegram,
|
|
|
96
102
|
|
|
97
103
|
{ hasFlex && (
|
|
98
104
|
<Option>
|
|
99
|
-
<
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
checked={ flex }
|
|
103
|
-
onChange={ (event) => onChangeFlex(event.target.checked) }
|
|
104
|
-
/>
|
|
105
|
+
<FlexHeader>
|
|
106
|
+
<FlexToggle type="button" aria-expanded={ open } onClick={ () => setOpen(!open) }>
|
|
107
|
+
<RiCalendarCheckLine />
|
|
105
108
|
|
|
106
|
-
|
|
109
|
+
{ t('routes_order.step5.flex.title') }
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
</label>
|
|
111
|
+
<FlexPrice>{ flexOffer?.price_display }</FlexPrice>
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
* asked to pay for flexibility, so what they get without paying
|
|
114
|
-
* has to be the thing they read before the price, not a
|
|
115
|
-
* disclosure buried under it.
|
|
116
|
-
*/}
|
|
117
|
-
<Notice>{ t('routes_order.step5.flex.default') }</Notice>
|
|
118
|
-
|
|
119
|
-
<Terms>
|
|
120
|
-
{ flexOffer?.refund_hours != null && (
|
|
121
|
-
<Term>{ t('routes_order.step5.flex.refund', { hours: flexOffer.refund_hours }) }</Term>
|
|
122
|
-
) }
|
|
113
|
+
{ open ? <RiArrowUpSLine /> : <RiArrowDownSLine /> }
|
|
114
|
+
</FlexToggle>
|
|
123
115
|
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
* between an offer and a misrepresentation.
|
|
129
|
-
*/}
|
|
130
|
-
{ flexOffer?.reschedule_hours != null && (
|
|
131
|
-
<Term>{ t('routes_order.step5.flex.reschedule', { hours: flexOffer.reschedule_hours }) }</Term>
|
|
132
|
-
) }
|
|
133
|
-
</Terms>
|
|
134
|
-
|
|
135
|
-
<Notice>{ t('routes_order.step5.flex.nonrefundable') }</Notice>
|
|
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>
|
|
136
120
|
|
|
137
121
|
{/*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
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.
|
|
142
128
|
*/}
|
|
143
|
-
<Notice>{ t('routes_order.step5.flex.
|
|
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
|
+
) }
|
|
144
160
|
</Option>
|
|
145
161
|
) }
|
|
162
|
+
|
|
146
163
|
</Container>
|
|
147
164
|
);
|
|
148
165
|
};
|
package/Step5/Addons/styles.ts
CHANGED
|
@@ -50,3 +50,62 @@ export const Term = styled.li`
|
|
|
50
50
|
font-size: 14px;
|
|
51
51
|
line-height: 1.5;
|
|
52
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
|
+
`;
|