@autobusal/routes-order 1.9.0 → 1.9.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +673 -0
  2. package/Checkout/Checkout.tsx +519 -0
  3. package/Checkout/Sidebar/Sidebar.tsx +124 -0
  4. package/Checkout/Sidebar/styles.ts +52 -0
  5. package/Facts/Facts.tsx +271 -0
  6. package/Facts/questions.ts +250 -0
  7. package/Facts/service.ts +111 -0
  8. package/Facts/services.ts +19 -0
  9. package/Facts/styles.ts +112 -0
  10. package/Facts/types.ts +167 -0
  11. package/Faq/Faq.tsx +52 -0
  12. package/Faq/styles.ts +68 -0
  13. package/Found/Dates/Dates.tsx +59 -5
  14. package/Found/Dates/styles.ts +137 -7
  15. package/Found/Empty/Empty.tsx +136 -0
  16. package/Found/Empty/styles.ts +98 -0
  17. package/Found/Filters/Filters.tsx +10 -7
  18. package/Found/Filters/styles.ts +79 -8
  19. package/Found/Found.tsx +128 -29
  20. package/Found/Group/Group.tsx +3 -1
  21. package/Found/Header/styles.ts +20 -4
  22. package/Found/Orders/Orders.tsx +25 -4
  23. package/Found/Picks/Picks.tsx +10 -3
  24. package/Found/Picks/styles.ts +60 -9
  25. package/Found/Route/Route.tsx +189 -33
  26. package/Found/Route/styles.ts +488 -50
  27. package/Found/SameDay/SameDay.tsx +35 -0
  28. package/Found/SameDay/styles.ts +27 -0
  29. package/Found/Sort/styles.ts +52 -7
  30. package/Found/Summary/Summary.tsx +1 -1
  31. package/Found/Summary/styles.ts +12 -0
  32. package/Found/Watch/Watch.tsx +187 -0
  33. package/Found/Watch/styles.ts +118 -0
  34. package/Found/refine.ts +69 -0
  35. package/Links/Links.tsx +91 -0
  36. package/Links/styles.ts +75 -0
  37. package/RoutesOrder.tsx +239 -31
  38. package/Schedule/Schedule.tsx +123 -0
  39. package/Schedule/styles.ts +71 -0
  40. package/Sections/Schema.tsx +186 -0
  41. package/Sections/Sections.tsx +160 -0
  42. package/Sections/styles.ts +44 -0
  43. package/Step1/prepare.ts +21 -2
  44. package/Step2.tsx +34 -1
  45. package/Step4/Passenger/Passenger.tsx +253 -24
  46. package/Step4/Passenger/styles.ts +21 -1
  47. package/Step4/Passengers/Display.tsx +10 -2
  48. package/Step4/Passengers/Passengers.tsx +53 -4
  49. package/Step4/Saved/Choose.tsx +99 -0
  50. package/Step4/Saved/Remember.tsx +58 -0
  51. package/Step4/Saved/styles.ts +87 -0
  52. package/Step4/checkAge.ts +15 -4
  53. package/Step5/Addons/Addons.tsx +146 -4
  54. package/Step5/Addons/styles.ts +94 -0
  55. package/Step5/Billing/Billing.tsx +138 -70
  56. package/Step5/Coupons/Coupons.tsx +28 -18
  57. package/Step5/Coupons/styles.ts +41 -8
  58. package/Step5/Payments/Payments.tsx +36 -5
  59. package/Step5/Payments/styles.ts +16 -7
  60. package/Step5/Summary/About/About.tsx +99 -27
  61. package/Step5/Summary/About/styles.ts +118 -1
  62. package/Step5/Summary/styles.ts +12 -5
  63. package/Step5/utilities.ts +67 -12
  64. package/Steps/Steps.tsx +3 -1
  65. package/commerce.ts +24 -0
  66. package/package.json +1 -1
  67. package/services.ts +295 -12
  68. package/styles.ts +126 -0
  69. package/types.ts +78 -1
  70. package/Found/Booking/Booking.tsx +0 -61
  71. package/Found/Booking/styles.ts +0 -50
  72. package/Step4/Step4.tsx +0 -114
  73. package/Step5/Step5.tsx +0 -185
@@ -1,50 +0,0 @@
1
- import { Link } from 'react-router-dom';
2
- import styled from 'styled-components';
3
-
4
- export const Container = styled.div`
5
- display: flex;
6
- flex-direction: column;
7
- gap: 15px;
8
- margin-bottom: 15px;
9
- padding: 15px;
10
- background: #003580;
11
- border-radius: ${ props => props.theme.borderRadius };
12
-
13
- @media (min-width: 480px) {
14
- flex-direction: row;
15
- align-items: center;
16
- }
17
- `;
18
-
19
- export const About = styled.div`
20
- color: #FFFFFF;
21
- font-weight: 700;
22
-
23
- > img {
24
- display: block;
25
- width: 110px;
26
- margin-top: 6px;
27
- }
28
- `;
29
-
30
- export const LinkBooking = styled(Link)`
31
- display: inline-block;
32
- max-width: 150px;
33
- padding: 6px 0;
34
- text-align: center;
35
- font-weight: 700;
36
- color: #333333;
37
- background: #FFFFFF;
38
- border-radius: 100px;
39
- transition: all 0.3s ease;
40
-
41
- &:hover {
42
- text-decoration: none;
43
- box-shadow: 0 4px 24px 0 rgba(255, 255, 255, 0.5);
44
- }
45
-
46
- @media (min-width: 480px) {
47
- flex-basis: 150px;
48
- margin-left: auto;
49
- }
50
- `;
package/Step4/Step4.tsx DELETED
@@ -1,114 +0,0 @@
1
- import { TFunction } from 'i18next';
2
- import { useForm } from 'react-hook-form';
3
- import { HiUsers } from 'react-icons/hi';
4
- import { RiShoppingCartLine } from 'react-icons/ri';
5
- import { AiOutlineCaretLeft } from 'react-icons/ai';
6
- import { useBusOccupied } from '@autobusal/hooks';
7
- import { Button } from '@autobusal/common';
8
- import Steps from '../Steps/Steps';
9
- import Loading from './Loading';
10
- import Passengers from './Passengers/Passengers';
11
- import checkAge from './checkAge';
12
- import { Title, Actions } from '../styles';
13
- import { RoutesSearchForm } from '@autobusal/routes-search/types';
14
- import { PersonData } from '@autobusal/providers/types/persons';
15
- import { FoundData } from '@autobusal/providers/types/routes';
16
-
17
- interface Props {
18
- values?: PersonData
19
- step1: RoutesSearchForm
20
- step2: FoundData
21
- step3?: FoundData
22
- isPartner?: boolean
23
- t: TFunction<'common'>
24
- onSave: (data: PersonData) => void
25
- onBack: (step: number) => void
26
- }
27
-
28
- // Edited: Ferjolt Ozuni - Date: 2026-07-25
29
- // Routes without a passenger_fields config keep the pre-feature form
30
- const DEFAULT_FIELDS = ['sex', 'passport'];
31
-
32
- const Step4 = ({ values, step1, step2, step3, isPartner, t, onSave, onBack }: Props): JSX.Element => {
33
- const { register, handleSubmit, formState: { errors }, setValue, setError } = useForm<PersonData>();
34
-
35
- // Edited: Ferjolt Ozuni - Date: 2026-07-25
36
- // one form serves both legs of a return trip, so it collects the UNION of
37
- // what either route requires (mirrored by the backend rules)
38
- const fields = Array.from(new Set([
39
- ...(step2.trip?.passenger_fields ?? DEFAULT_FIELDS),
40
- ...(step3 !== undefined ? (step3.trip?.passenger_fields ?? DEFAULT_FIELDS) : [])
41
- ]));
42
-
43
- // Edited: Ferjolt Ozuni - Date: 2026-07-27
44
- // FoundData.id is widened to (number | string) for external-provider
45
- // offers (Mode A: search + redirect), but those never reach Step4 -
46
- // Found/Route.tsx's CTA opens the provider's click-out link for them
47
- // instead of calling onSave, so a real local numeric id is guaranteed here.
48
- const busFrom = useBusOccupied(step1.departure, step2.id as number);
49
- const busTo = useBusOccupied(step1._return, step3?.id as (number | undefined));
50
-
51
- if (busFrom === undefined || busTo === undefined) {
52
- return <Loading t={ t } />;
53
- }
54
-
55
- const onSubmit = (data: PersonData): void => {
56
- // if all the ages are ok, we move forward
57
- if (checkAge(step1, data, t)) {
58
- onSave(data);
59
- }
60
- };
61
-
62
- const onUpdate = (name: string, value: (string | number)): void => {
63
- setValue(name, value);
64
- setError(name, {});
65
- };
66
-
67
- const onSendForm = (): void => {
68
- handleSubmit(onSubmit)();
69
- };
70
-
71
- return (
72
- <>
73
- <Title>
74
- <HiUsers />
75
- { t('routes_order.step4.title') }
76
- </Title>
77
-
78
- <Steps value={ 4 } />
79
-
80
- <Passengers
81
- values={ values }
82
- step1={ step1 }
83
- fields={ fields }
84
- busFrom={ busFrom }
85
- busTo={ busTo }
86
- t={ t }
87
- errors={ errors }
88
- refs={ register }
89
- onUpdate={ onUpdate }
90
- />
91
-
92
- <Actions>
93
- { isPartner === undefined && (
94
- <Button
95
- type="button"
96
- subtype="secondary"
97
- text={ <><AiOutlineCaretLeft /> { t('routes_order.step4.back') }</> }
98
- noMargin
99
- onClick={ () => onBack(step1._return ? 3 : 2) }
100
- />
101
- ) }
102
-
103
- <Button
104
- type="button"
105
- text={ <><RiShoppingCartLine /> { t('routes_order.step4.next') }</> }
106
- noMargin
107
- onClick={ onSendForm }
108
- />
109
- </Actions>
110
- </>
111
- );
112
- };
113
-
114
- export default Step4;
package/Step5/Step5.tsx DELETED
@@ -1,185 +0,0 @@
1
- import { useCallback, useState } from 'react';
2
- import { TFunction } from 'i18next';
3
- import { useForm } from 'react-hook-form';
4
- import { useNavigate } from 'react-router-dom';
5
- import { RiShoppingCartLine } from 'react-icons/ri';
6
- import { AiOutlineCaretLeft } from 'react-icons/ai';
7
- import { MdDone } from 'react-icons/md';
8
- import { Button } from '@autobusal/common';
9
- import Steps from '../Steps/Steps';
10
- import Summary from './Summary/Summary';
11
- import Coupons from './Coupons/Coupons';
12
- import Addons from './Addons/Addons';
13
- import Billing from './Billing/Billing';
14
- import Payments from './Payments/Payments';
15
- import { getTotal, convertCoupon } from './utilities';
16
- import { Title, Actions } from '../styles';
17
- import { PersonData, BillingData } from '@autobusal/providers/types/persons';
18
- import { FoundData } from '@autobusal/providers/types/routes';
19
- import { CouponData } from '@autobusal/providers/types/orders';
20
- import { RoutesSearchForm } from '@autobusal/routes-search/types';
21
- import { TotalData } from '../types';
22
- import { useUserStore } from '@autobusal/providers/stores/user';
23
- import { useGetSettings } from '@autobusal/providers/services';
24
- import { usePostOrder } from '../services';
25
-
26
- interface Props {
27
- step1: RoutesSearchForm
28
- step2: FoundData
29
- step3?: FoundData
30
- step4: PersonData
31
- isPartner?: boolean
32
- t: TFunction<'common'>
33
- onBack: (step: number) => void
34
- }
35
-
36
- const Step5 = ({ step1, step2, step3, step4, isPartner, t, onBack }: Props): JSX.Element => {
37
- const [ coupon, setCoupon ] = useState<CouponData | undefined>(undefined);
38
- const [ total, setTotal ] = useState<TotalData>(
39
- getTotal(step2, step3)
40
- );
41
- const [ action, setAction ] = useState<string | undefined>(undefined);
42
-
43
- const [ addonWhatsapp, setAddonWhatsapp ] = useState<boolean>(false);
44
- const [ addonWhatsappNumber, setAddonWhatsappNumber ] = useState<string>('');
45
- const [ addonTelegram, setAddonTelegram ] = useState<boolean>(false);
46
-
47
- const { data: UserData } = useUserStore();
48
- const { data: SettingsData } = useGetSettings();
49
-
50
- const navigate = useNavigate();
51
-
52
- const { register, handleSubmit, formState: { errors } } = useForm<BillingData>();
53
-
54
- // Edited: Ferjolt Ozuni - Date: 2026-07-25
55
- // Paid addons are priced/verified server-side (see obtapi's
56
- // App\Libraries\Orders\Make\Addons) - this is only for the checkout
57
- // summary display and the grand total shown before payment.
58
- const currency = total.display.split(' ')[1] ?? '';
59
-
60
- const addonItems = [
61
- addonWhatsapp && SettingsData?.addons?.whatsapp.available ? {
62
- label: t('routes_order.step5.addons.whatsapp.label'),
63
- price: SettingsData.addons.whatsapp.price
64
- } : null,
65
- addonTelegram && SettingsData?.addons?.telegram.available ? {
66
- label: t('routes_order.step5.addons.telegram.label'),
67
- price: SettingsData.addons.telegram.price
68
- } : null
69
- ].filter((item): item is { label: string, price: number } => item !== null);
70
-
71
- const addonsValue = addonItems.reduce((sum, item) => sum + item.price, 0);
72
-
73
- const grandTotal: TotalData = {
74
- display: `${ total.value + addonsValue } ${ currency }`,
75
- value: total.value + addonsValue
76
- };
77
-
78
- const { mutate: OrderSend, isPending } = usePostOrder(step1, step2, step3, step4, coupon, action, {
79
- whatsapp: addonWhatsapp,
80
- whatsappNumber: addonWhatsappNumber,
81
- telegram: addonTelegram
82
- });
83
-
84
- const onApplyCoupon = (data: CouponData): void => {
85
- setTotal(
86
- convertCoupon(data)
87
- );
88
-
89
- setCoupon(data);
90
- };
91
-
92
- const onAction = useCallback((action: string): void => {
93
- setAction(action);
94
- }, []);
95
-
96
- const onSendForm = async (): Promise<void> => {
97
- if (addonWhatsapp && addonWhatsappNumber.trim().length < 5) {
98
- alert(t('routes_order.step5.addons.whatsapp.phone_error'));
99
-
100
- return;
101
- }
102
-
103
- await handleSubmit(onSubmit)();
104
- };
105
-
106
- const onSubmit = (data: BillingData): void => {
107
- OrderSend(data, {
108
- onSuccess: (data) => {
109
- if (data === undefined) {
110
- return;
111
- }
112
-
113
- const hash = step1.type === 'return' ? data.return : data.departure;
114
-
115
- navigate(`/orders/process/${ action }/${ hash }`);
116
- }
117
- });
118
- };
119
-
120
- return (
121
- <>
122
- <Title>
123
- <RiShoppingCartLine />
124
- { t('routes_order.step5.title') }
125
- </Title>
126
-
127
- <Steps value={ 5 } />
128
-
129
- <Summary
130
- step1={ step1 }
131
- step2={ step2 }
132
- step3={ step3 }
133
- step4={ step4 }
134
- coupon={ coupon }
135
- addons={ addonItems.map(item => ({ label: item.label, value: `${ item.price } ${ currency }` })) }
136
- total={ grandTotal }
137
- t={ t }
138
- />
139
-
140
- <Coupons current={ coupon } step2={ step2 } total={ total } t={ t } onApplied={ onApplyCoupon } />
141
-
142
- <Addons
143
- data={ SettingsData?.addons }
144
- currency={ currency }
145
- whatsapp={ addonWhatsapp }
146
- whatsappNumber={ addonWhatsappNumber }
147
- telegram={ addonTelegram }
148
- t={ t }
149
- onChangeWhatsapp={ setAddonWhatsapp }
150
- onChangeWhatsappNumber={ setAddonWhatsappNumber }
151
- onChangeTelegram={ setAddonTelegram }
152
- />
153
-
154
- { UserData === undefined && <Billing t={ t } errors={ errors } refs={ register } /> }
155
-
156
- <Payments
157
- selected={ action ?? '' }
158
- isPartner={ isPartner ?? false }
159
- t={ t }
160
- onChange={ onAction }
161
- />
162
-
163
- <Actions>
164
- <Button
165
- type="button"
166
- subtype="secondary"
167
- text={ <><AiOutlineCaretLeft /> { t('routes_order.step5.back') }</> }
168
- noMargin
169
- onClick={ () => onBack(4) }
170
- />
171
-
172
- <Button
173
- loading={isPending}
174
- noMargin
175
- text={ (
176
- <><MdDone />{ t('routes_order.step5.next') }</>
177
- ) }
178
- onClick={ onSendForm }
179
- />
180
- </Actions>
181
- </>
182
- );
183
- };
184
-
185
- export default Step5;