@autobusal/routes-search 0.0.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.
@@ -0,0 +1,187 @@
1
+ import { useState } from 'react';
2
+ import { TFunction } from 'i18next';
3
+ import { useForm } from 'react-hook-form';
4
+ import { FaArrowUp, FaHistory } from 'react-icons/fa';
5
+ import { AiOutlineSearch } from 'react-icons/ai';
6
+ import { Button } from '@autobusal/common';
7
+ import { display, createDate } from '@autobusal/utilities';
8
+ import { Autocomplete, Calendar, Passengers } from '@autobusal/common';
9
+ import { Options, Columns, Column, Item, Title, Action } from './styles';
10
+ import { RoutesSearchForm } from './types';
11
+ import { AutocompleteData } from '@autobusal/common/Autocomplete/types';
12
+ import { GetSuggestions } from './services';
13
+
14
+ interface Props {
15
+ type: 'departure' | 'return'
16
+ from: string
17
+ to: string
18
+ departure: string
19
+ _return: string
20
+ adults: number
21
+ children: number
22
+ babies: number
23
+ t: TFunction<'common'>
24
+ onSearch: (data: RoutesSearchForm) => void
25
+ }
26
+
27
+ const RoutesSearch = ({ type, from, to, departure, _return, adults, children, babies, t, onSearch }: Props): JSX.Element => {
28
+ const [ selected, setSelected ] = useState<'departure' | 'return'>(type);
29
+
30
+ const { register, handleSubmit, formState: { errors }, setValue } = useForm<RoutesSearchForm>();
31
+
32
+ const { data } = GetSuggestions();
33
+
34
+ const onClick = (type: ('departure' | 'return')): void => {
35
+ setSelected(type);
36
+ };
37
+
38
+ const onSubmit = (data: RoutesSearchForm): void => {
39
+ const departureDate = createDate(data.departure);
40
+ const _returnDate = createDate(data._return ?? '');
41
+
42
+ data.type = selected;
43
+
44
+ // arrange the data, as some get transformed to strings
45
+ data.adults = Number(data.adults);
46
+ data.babies = Number(data.babies);
47
+ data.children = Number(data.children);
48
+
49
+ if (data.adults == 0) {
50
+ alert(t('general:search_routes.errors.adults'));
51
+ } else if (data.adults + data.children > 5) {
52
+ alert(t('general:search_routes.errors.max_tickets', { max: 5 }));
53
+ } else if (data.babies > data.adults) {
54
+ alert(t('general:search_routes.errors.babies'));
55
+ } else if (selected === 'return' && departureDate > _returnDate) {
56
+ alert(t('general:search_routes.errors.dates'));
57
+ } else {
58
+ onSearch(data);
59
+ }
60
+ };
61
+
62
+ const cities: AutocompleteData[] = data.map(city => ({
63
+ name: `${ city.name }, ${ city.country?.name ?? '' }`,
64
+ value: city.slug
65
+ }));
66
+
67
+ return (
68
+ <form onSubmit={ handleSubmit(onSubmit) }>
69
+ <Options>
70
+ <Button
71
+ type="button"
72
+ size="medium"
73
+ active={ selected === 'departure' }
74
+ text={ <><FaArrowUp /> { t('routes_search.options.departure', { ns: 'common' }) }</> }
75
+ noMargin
76
+ onClick={ () => onClick('departure') }
77
+ />
78
+
79
+ <Button
80
+ type="button"
81
+ size="medium"
82
+ active={ selected === 'return' }
83
+ text={ <><FaHistory />{ t('routes_search.options.return', { ns: 'common' }) }</> }
84
+ noMargin
85
+ onClick={ () => onClick('return') }
86
+ />
87
+ </Options>
88
+
89
+ <Columns>
90
+ <Column>
91
+ <Item>
92
+ <Title>{ t('routes_search.from', { ns: 'common' }) }</Title>
93
+
94
+ <Autocomplete
95
+ name="from"
96
+ defaultValue={ from }
97
+ data={ cities }
98
+ validation="required"
99
+ t={ t }
100
+ refs={ register }
101
+ onUpdate={ setValue }
102
+ />
103
+
104
+ { display(errors.from) }
105
+ </Item>
106
+
107
+ <Item>
108
+ <Title>{ t('routes_search.to', { ns: 'common' }) }</Title>
109
+
110
+ <Autocomplete
111
+ name="to"
112
+ defaultValue={ to }
113
+ data={ cities }
114
+ validation="required"
115
+ t={ t }
116
+ refs={ register }
117
+ onUpdate={ setValue }
118
+ />
119
+
120
+ { display(errors.to) }
121
+ </Item>
122
+ </Column>
123
+
124
+ <Column>
125
+ <Item>
126
+ <Title>{ t('routes_search.departure', { ns: 'common' }) }</Title>
127
+
128
+ <Calendar
129
+ type="picker"
130
+ name="departure"
131
+ defaultValue={ departure }
132
+ t={ t }
133
+ validation="required"
134
+ refs={ register }
135
+ onUpdate={ setValue }
136
+ />
137
+
138
+ { display(errors.departure) }
139
+ </Item>
140
+
141
+ { selected === 'return' && (
142
+ <Item>
143
+ <Title>{ t('routes_search.return', { ns: 'common' }) }</Title>
144
+
145
+ <Calendar
146
+ type="picker"
147
+ name="_return"
148
+ defaultValue={ _return }
149
+ t={ t }
150
+ validation="required"
151
+ refs={ register }
152
+ onUpdate={ setValue }
153
+ />
154
+
155
+ { display(errors._return) }
156
+ </Item>
157
+ ) }
158
+ </Column>
159
+
160
+ <Column>
161
+ <Item>
162
+ <Title>{ t('routes_search.passengers', { ns: 'common' }) }</Title>
163
+
164
+ <Passengers
165
+ defaultAdults={ adults }
166
+ defaultChildren={ children }
167
+ defaultBabies={ babies }
168
+ t={ t }
169
+ refs={ register }
170
+ onUpdate={ setValue }
171
+ />
172
+ </Item>
173
+
174
+ <Action>
175
+ <Button
176
+ type="submit"
177
+ noMargin
178
+ text={ <><AiOutlineSearch /> { t('routes_search.action', { ns: 'common' }) }</> }
179
+ />
180
+ </Action>
181
+ </Column>
182
+ </Columns>
183
+ </form>
184
+ );
185
+ }
186
+
187
+ export default RoutesSearch;
package/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import RoutesSearch from './RoutesSearch';
2
+
3
+ export default RoutesSearch;
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@autobusal/routes-search",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "index.ts",
6
+ "dependencies": {
7
+ "@autobusal/common": "^0.0.18",
8
+ "@autobusal/providers": "^1.0.16",
9
+ "@autobusal/utilities": "^0.0.2",
10
+ "@tanstack/react-query": "^5.17.19",
11
+ "i18next": "^23.7.18",
12
+ "react": "^18.2.0",
13
+ "react-hook-form": "^7.49.3",
14
+ "react-icons": "^5.0.1",
15
+ "styled-components": "^6.1.8"
16
+ }
17
+ }
package/services.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { useSuspenseQuery, UseSuspenseQueryResult } from '@tanstack/react-query';
2
+ import { apiClient } from '@autobusal/providers';
3
+ import { CityData } from '@autobusal/providers/types/locations';
4
+
5
+ export const GetSuggestions = (): UseSuspenseQueryResult<CityData[]> => (
6
+ useSuspenseQuery({
7
+ queryKey: ['search-suggestions'],
8
+ queryFn: async () => (
9
+ await apiClient
10
+ .get('/api/cities/browse')
11
+ .then(response => (
12
+ response?.data ?? []
13
+ ))
14
+ .catch(() => {})
15
+ )
16
+ })
17
+ );
package/styles.ts ADDED
@@ -0,0 +1,49 @@
1
+ import styled from 'styled-components';
2
+
3
+ export const Options = styled.div`
4
+ display: flex;
5
+ gap: 15px;
6
+ margin-bottom: 20px;
7
+ `;
8
+
9
+ export const Columns = styled.div`
10
+ display: flex;
11
+ flex-wrap: wrap;
12
+ gap: 15px;
13
+ `;
14
+
15
+ export const Column = styled.div`
16
+ flex-basis: 100%;
17
+ display: flex;
18
+ flex-direction: column;
19
+ gap: 15px;
20
+
21
+ @media (min-width: 480px) {
22
+ flex-direction: row;
23
+ }
24
+
25
+ @media (min-width: 768px) {
26
+ flex-direction: column;
27
+ flex-basis: calc(33.3333% - 10px);
28
+ }
29
+ `;
30
+
31
+ export const Item = styled.div`
32
+ flex: 1;
33
+ `;
34
+
35
+ export const Title = styled.h4`
36
+ margin-bottom: 8px;
37
+ font-size: ${ props => props.theme.size.s };
38
+ `;
39
+
40
+ export const Action = styled.div`
41
+ flex: 1;
42
+ display: flex;
43
+ justify-content: center;
44
+ align-items: end;
45
+
46
+ @media (min-width: 640px) {
47
+ justify-content: right;
48
+ }
49
+ `;
package/types.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface RoutesSearchForm {
2
+ type?: 'departure' | 'return'
3
+ from: string
4
+ to: string
5
+ departure: string
6
+ _return?: string
7
+ adults: number
8
+ children: number
9
+ babies: number
10
+ }