@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 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.**
@@ -1,8 +1,9 @@
1
1
  import { TFunction } from 'i18next';
2
- import { RiWhatsappLine, RiTelegramLine, RiCalendarCheckLine } from 'react-icons/ri';
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
- <label>
100
- <input
101
- type="checkbox"
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
- <RiCalendarCheckLine />
109
+ { t('routes_order.step5.flex.title') }
107
110
 
108
- { t('routes_order.step5.flex.add', { price: flexOffer?.price_display }) }
109
- </label>
111
+ <FlexPrice>{ flexOffer?.price_display }</FlexPrice>
110
112
 
111
- {/*
112
- * The default is stated FIRST and plainly. The passenger is being
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
- * Either half can be absent on its own. An operator who will
126
- * move a booking but not refund it is a real case, and listing
127
- * only what this booking actually includes is the difference
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
- * Statutory rights when the OPERATOR cancels are untouched by
139
- * this and by anything else sold here. Saying so at the point of
140
- * sale is both accurate and reassuring; leaving it out would
141
- * imply the add-on is what grants them.
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.carrier') }</Notice>
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
  };
@@ -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
+ `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autobusal/routes-order",
3
- "version": "1.23.0",
3
+ "version": "1.23.1",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"