@autobusal/routes-order 1.23.0 → 1.23.2

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.23.2
4
+
5
+ - Flexible Ticket detail is now behind a small **Read More** link rather than the title. Its own control, so the thing you click to *read* is never next to the thing you click to *buy*. Closed by default; one line — "without it, this ticket cannot be cancelled or changed" — stays outside the collapse, because that is the material fact and hiding it would be a dark pattern rather than a tidy layout.
6
+ - **Date strip: dropped the joining comma.** Above 1024px the day number carried a `::before` comma so the cell would read "Mon, 03 Aug" on one line. The cells are narrow enough that the two parts wrap anyway, so the comma led the second line and every desktop cell rendered as "Mon" over ", 03 Aug". Punctuation that only works when text happens not to wrap will keep breaking.
7
+
8
+ ## 1.23.1
9
+
10
+ **Flexible Ticket as a title, a price and an Add button, with the detail collapsed.**
11
+
12
+ 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.
13
+
14
+ 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.
15
+
3
16
  ## 1.23.0
4
17
 
5
18
  **Flexible Ticket at checkout.**
@@ -99,12 +99,20 @@ export const DayNumber = styled.span`
99
99
  display: block;
100
100
  line-height: 1.2;
101
101
 
102
+ /*
103
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
104
+ *
105
+ * NO JOINING COMMA. This carried a ::before comma above 1024px, to read
106
+ * "Mon, 03 Aug" on one line. The cells are narrow enough
107
+ * that the weekday and the date wrap anyway, and the comma then led the
108
+ * second line - every desktop cell rendered as "Mon" over ", 03 Aug".
109
+ *
110
+ * Punctuation that only works when the text happens not to wrap is
111
+ * punctuation that will keep breaking; the two parts read perfectly well
112
+ * stacked without it.
113
+ */
102
114
  @media (min-width: 1024px) {
103
115
  display: inline;
104
-
105
- &::before {
106
- content: ', ';
107
- }
108
116
  }
109
117
  `;
110
118
 
@@ -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, FlexTitle, 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,68 @@ 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
+ <FlexTitle>
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>
112
+ </FlexTitle>
113
+
114
+ <FlexAdd type="button" $added={ flex } onClick={ () => onChangeFlex(!flex) }>
115
+ { flex ? t('routes_order.step5.flex.remove') : t('routes_order.step5.flex.add') }
116
+ </FlexAdd>
117
+ </FlexHeader>
110
118
 
111
119
  {/*
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.
120
+ * Deliberately OUTSIDE the collapse. Everything else here is
121
+ * detail somebody can choose to read, but this one line is the
122
+ * material fact - a buyer is being asked to pay for a right they
123
+ * would otherwise not have, and hiding what they get for nothing
124
+ * behind a toggle is the shape of a dark pattern rather than a
125
+ * tidy layout.
116
126
  */}
117
127
  <Notice>{ t('routes_order.step5.flex.default') }</Notice>
118
128
 
119
- <Terms>
120
- { flexOffer?.refund_hours != null && (
121
- <Term>{ t('routes_order.step5.flex.refund', { hours: flexOffer.refund_hours }) }</Term>
122
- ) }
123
-
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>
136
-
137
- {/*
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.
142
- */}
143
- <Notice>{ t('routes_order.step5.flex.carrier') }</Notice>
129
+ <FlexToggle type="button" aria-expanded={ open } onClick={ () => setOpen(!open) }>
130
+ { open ? t('routes_order.step5.flex.less') : t('routes_order.step5.flex.more') }
131
+
132
+ { open ? <RiArrowUpSLine /> : <RiArrowDownSLine /> }
133
+ </FlexToggle>
134
+
135
+ { open && (
136
+ <FlexDetails>
137
+ <Terms>
138
+ { flexOffer?.refund_hours != null && (
139
+ <Term>{ t('routes_order.step5.flex.refund', { hours: flexOffer.refund_hours }) }</Term>
140
+ ) }
141
+
142
+ {/*
143
+ * Either half can be absent on its own. An operator who
144
+ * will move a booking but not refund it is a real case, and
145
+ * listing only what this booking actually includes is the
146
+ * difference between an offer and a misrepresentation.
147
+ */}
148
+ { flexOffer?.reschedule_hours != null && (
149
+ <Term>{ t('routes_order.step5.flex.reschedule', { hours: flexOffer.reschedule_hours }) }</Term>
150
+ ) }
151
+ </Terms>
152
+
153
+ <Notice>{ t('routes_order.step5.flex.nonrefundable') }</Notice>
154
+
155
+ {/*
156
+ * Statutory rights when the OPERATOR cancels are untouched by
157
+ * this and by anything else sold here. Saying so is both
158
+ * accurate and reassuring; leaving it out would imply the
159
+ * add-on is what grants them.
160
+ */}
161
+ <Notice>{ t('routes_order.step5.flex.carrier') }</Notice>
162
+ </FlexDetails>
163
+ ) }
144
164
  </Option>
145
165
  ) }
166
+
146
167
  </Container>
147
168
  );
148
169
  };
@@ -50,3 +50,77 @@ 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 FlexTitle = styled.span`
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 6px;
76
+ font-weight: 700;
77
+ `;
78
+
79
+ /**
80
+ * "Read More", small and quiet, under the one line that always shows.
81
+ *
82
+ * Edited: Ferjolt Ozuni - Date: 2026-08-03
83
+ * Its own control rather than the title, so the thing you click to READ is
84
+ * never next to the thing you click to BUY.
85
+ */
86
+ export const FlexToggle = styled.button`
87
+ display: inline-flex;
88
+ align-items: center;
89
+ gap: 2px;
90
+ margin-top: 4px;
91
+ margin-left: 22px;
92
+ padding: 0;
93
+ border: 0;
94
+ background: none;
95
+ color: ${ props => props.theme.primary.normal };
96
+ font-family: inherit;
97
+ font-size: ${ props => props.theme.size.xs };
98
+ cursor: pointer;
99
+ text-decoration: underline;
100
+ `;
101
+
102
+ export const FlexPrice = styled.span`
103
+ font-weight: 700;
104
+ white-space: nowrap;
105
+ `;
106
+
107
+ export const FlexAdd = styled.button<{ $added: boolean }>`
108
+ flex: 0 0 auto;
109
+ padding: 6px 16px;
110
+ border-radius: 4px;
111
+ border: 1px solid ${ props => props.theme.primary.normal };
112
+ background: ${ props => props.$added ? 'transparent' : props.theme.primary.normal };
113
+ color: ${ props => props.$added ? props.theme.primary.normal : props.theme.primary.contrast };
114
+ font-family: inherit;
115
+ font-size: ${ props => props.theme.size.xs };
116
+ font-weight: 700;
117
+ cursor: pointer;
118
+
119
+ &:hover {
120
+ opacity: 0.85;
121
+ }
122
+ `;
123
+
124
+ export const FlexDetails = styled.div`
125
+ margin-left: 22px;
126
+ `;
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.2",
4
4
  "author": "Ferjolt Ozuni",
5
5
  "type": "module",
6
6
  "main": "index.ts"