ubkerp.transport.module 0.1.0

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 (152) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +136 -0
  3. package/lib/commonjs/assets/index.js +6 -0
  4. package/lib/commonjs/assets/index.js.map +1 -0
  5. package/lib/commonjs/components/index.js +6 -0
  6. package/lib/commonjs/components/index.js.map +1 -0
  7. package/lib/commonjs/constants/index.js +6 -0
  8. package/lib/commonjs/constants/index.js.map +1 -0
  9. package/lib/commonjs/hooks/index.js +6 -0
  10. package/lib/commonjs/hooks/index.js.map +1 -0
  11. package/lib/commonjs/index.js +34 -0
  12. package/lib/commonjs/index.js.map +1 -0
  13. package/lib/commonjs/navigation/index.js +6 -0
  14. package/lib/commonjs/navigation/index.js.map +1 -0
  15. package/lib/commonjs/package.json +1 -0
  16. package/lib/commonjs/providers/TransportContext.js +9 -0
  17. package/lib/commonjs/providers/TransportContext.js.map +1 -0
  18. package/lib/commonjs/providers/TransportProvider.js +46 -0
  19. package/lib/commonjs/providers/TransportProvider.js.map +1 -0
  20. package/lib/commonjs/providers/types.js +6 -0
  21. package/lib/commonjs/providers/types.js.map +1 -0
  22. package/lib/commonjs/providers/useTransport.js +16 -0
  23. package/lib/commonjs/providers/useTransport.js.map +1 -0
  24. package/lib/commonjs/screens/StudentRouteDetails/StudentRouteDetailsScreen.js +308 -0
  25. package/lib/commonjs/screens/StudentRouteDetails/StudentRouteDetailsScreen.js.map +1 -0
  26. package/lib/commonjs/screens/TransportDashboard/TransportDashboard.js +87 -0
  27. package/lib/commonjs/screens/TransportDashboard/TransportDashboard.js.map +1 -0
  28. package/lib/commonjs/services/api/createApiClient.js +116 -0
  29. package/lib/commonjs/services/api/createApiClient.js.map +1 -0
  30. package/lib/commonjs/services/api/types.js +6 -0
  31. package/lib/commonjs/services/api/types.js.map +1 -0
  32. package/lib/commonjs/services/index.js +6 -0
  33. package/lib/commonjs/services/index.js.map +1 -0
  34. package/lib/commonjs/services/transport/studentRouteDetails.js +10 -0
  35. package/lib/commonjs/services/transport/studentRouteDetails.js.map +1 -0
  36. package/lib/commonjs/services/transport/types.js +2 -0
  37. package/lib/commonjs/services/transport/types.js.map +1 -0
  38. package/lib/commonjs/store/index.js +6 -0
  39. package/lib/commonjs/store/index.js.map +1 -0
  40. package/lib/commonjs/types/index.js +2 -0
  41. package/lib/commonjs/types/index.js.map +1 -0
  42. package/lib/commonjs/types/transport.js +2 -0
  43. package/lib/commonjs/types/transport.js.map +1 -0
  44. package/lib/commonjs/utils/index.js +6 -0
  45. package/lib/commonjs/utils/index.js.map +1 -0
  46. package/lib/module/assets/index.js +4 -0
  47. package/lib/module/assets/index.js.map +1 -0
  48. package/lib/module/components/index.js +4 -0
  49. package/lib/module/components/index.js.map +1 -0
  50. package/lib/module/constants/index.js +4 -0
  51. package/lib/module/constants/index.js.map +1 -0
  52. package/lib/module/hooks/index.js +4 -0
  53. package/lib/module/hooks/index.js.map +1 -0
  54. package/lib/module/index.js +7 -0
  55. package/lib/module/index.js.map +1 -0
  56. package/lib/module/navigation/index.js +4 -0
  57. package/lib/module/navigation/index.js.map +1 -0
  58. package/lib/module/package.json +1 -0
  59. package/lib/module/providers/TransportContext.js +5 -0
  60. package/lib/module/providers/TransportContext.js.map +1 -0
  61. package/lib/module/providers/TransportProvider.js +41 -0
  62. package/lib/module/providers/TransportProvider.js.map +1 -0
  63. package/lib/module/providers/types.js +4 -0
  64. package/lib/module/providers/types.js.map +1 -0
  65. package/lib/module/providers/useTransport.js +12 -0
  66. package/lib/module/providers/useTransport.js.map +1 -0
  67. package/lib/module/screens/StudentRouteDetails/StudentRouteDetailsScreen.js +303 -0
  68. package/lib/module/screens/StudentRouteDetails/StudentRouteDetailsScreen.js.map +1 -0
  69. package/lib/module/screens/TransportDashboard/TransportDashboard.js +82 -0
  70. package/lib/module/screens/TransportDashboard/TransportDashboard.js.map +1 -0
  71. package/lib/module/services/api/createApiClient.js +111 -0
  72. package/lib/module/services/api/createApiClient.js.map +1 -0
  73. package/lib/module/services/api/types.js +4 -0
  74. package/lib/module/services/api/types.js.map +1 -0
  75. package/lib/module/services/index.js +4 -0
  76. package/lib/module/services/index.js.map +1 -0
  77. package/lib/module/services/transport/studentRouteDetails.js +6 -0
  78. package/lib/module/services/transport/studentRouteDetails.js.map +1 -0
  79. package/lib/module/services/transport/types.js +2 -0
  80. package/lib/module/services/transport/types.js.map +1 -0
  81. package/lib/module/store/index.js +4 -0
  82. package/lib/module/store/index.js.map +1 -0
  83. package/lib/module/types/index.js +2 -0
  84. package/lib/module/types/index.js.map +1 -0
  85. package/lib/module/types/transport.js +2 -0
  86. package/lib/module/types/transport.js.map +1 -0
  87. package/lib/module/utils/index.js +4 -0
  88. package/lib/module/utils/index.js.map +1 -0
  89. package/lib/typescript/assets/index.d.ts +2 -0
  90. package/lib/typescript/assets/index.d.ts.map +1 -0
  91. package/lib/typescript/components/index.d.ts +2 -0
  92. package/lib/typescript/components/index.d.ts.map +1 -0
  93. package/lib/typescript/constants/index.d.ts +2 -0
  94. package/lib/typescript/constants/index.d.ts.map +1 -0
  95. package/lib/typescript/hooks/index.d.ts +2 -0
  96. package/lib/typescript/hooks/index.d.ts.map +1 -0
  97. package/lib/typescript/index.d.ts +8 -0
  98. package/lib/typescript/index.d.ts.map +1 -0
  99. package/lib/typescript/navigation/index.d.ts +2 -0
  100. package/lib/typescript/navigation/index.d.ts.map +1 -0
  101. package/lib/typescript/providers/TransportContext.d.ts +8 -0
  102. package/lib/typescript/providers/TransportContext.d.ts.map +1 -0
  103. package/lib/typescript/providers/TransportProvider.d.ts +3 -0
  104. package/lib/typescript/providers/TransportProvider.d.ts.map +1 -0
  105. package/lib/typescript/providers/types.d.ts +28 -0
  106. package/lib/typescript/providers/types.d.ts.map +1 -0
  107. package/lib/typescript/providers/useTransport.d.ts +2 -0
  108. package/lib/typescript/providers/useTransport.d.ts.map +1 -0
  109. package/lib/typescript/screens/StudentRouteDetails/StudentRouteDetailsScreen.d.ts +6 -0
  110. package/lib/typescript/screens/StudentRouteDetails/StudentRouteDetailsScreen.d.ts.map +1 -0
  111. package/lib/typescript/screens/TransportDashboard/TransportDashboard.d.ts +12 -0
  112. package/lib/typescript/screens/TransportDashboard/TransportDashboard.d.ts.map +1 -0
  113. package/lib/typescript/services/api/createApiClient.d.ts +18 -0
  114. package/lib/typescript/services/api/createApiClient.d.ts.map +1 -0
  115. package/lib/typescript/services/api/types.d.ts +18 -0
  116. package/lib/typescript/services/api/types.d.ts.map +1 -0
  117. package/lib/typescript/services/index.d.ts +2 -0
  118. package/lib/typescript/services/index.d.ts.map +1 -0
  119. package/lib/typescript/services/transport/studentRouteDetails.d.ts +5 -0
  120. package/lib/typescript/services/transport/studentRouteDetails.d.ts.map +1 -0
  121. package/lib/typescript/services/transport/types.d.ts +7 -0
  122. package/lib/typescript/services/transport/types.d.ts.map +1 -0
  123. package/lib/typescript/store/index.d.ts +2 -0
  124. package/lib/typescript/store/index.d.ts.map +1 -0
  125. package/lib/typescript/types/index.d.ts +2 -0
  126. package/lib/typescript/types/index.d.ts.map +1 -0
  127. package/lib/typescript/types/transport.d.ts +12 -0
  128. package/lib/typescript/types/transport.d.ts.map +1 -0
  129. package/lib/typescript/utils/index.d.ts +2 -0
  130. package/lib/typescript/utils/index.d.ts.map +1 -0
  131. package/package.json +61 -0
  132. package/src/assets/index.ts +2 -0
  133. package/src/components/index.ts +2 -0
  134. package/src/constants/index.ts +2 -0
  135. package/src/hooks/index.ts +2 -0
  136. package/src/index.ts +10 -0
  137. package/src/navigation/index.ts +2 -0
  138. package/src/providers/TransportContext.ts +11 -0
  139. package/src/providers/TransportProvider.tsx +47 -0
  140. package/src/providers/types.ts +28 -0
  141. package/src/providers/useTransport.ts +11 -0
  142. package/src/screens/StudentRouteDetails/StudentRouteDetailsScreen.tsx +284 -0
  143. package/src/screens/TransportDashboard/TransportDashboard.tsx +93 -0
  144. package/src/services/api/createApiClient.ts +170 -0
  145. package/src/services/api/types.ts +30 -0
  146. package/src/services/index.ts +2 -0
  147. package/src/services/transport/studentRouteDetails.ts +10 -0
  148. package/src/services/transport/types.ts +7 -0
  149. package/src/store/index.ts +2 -0
  150. package/src/types/index.ts +2 -0
  151. package/src/types/transport.ts +12 -0
  152. package/src/utils/index.ts +2 -0
@@ -0,0 +1,284 @@
1
+ import React, {useCallback, useEffect, useMemo, useState} from 'react';
2
+ import {
3
+ ActivityIndicator,
4
+ Alert,
5
+ Linking,
6
+ ScrollView,
7
+ StyleSheet,
8
+ Text,
9
+ TouchableOpacity,
10
+ View,
11
+ Image,
12
+ } from 'react-native';
13
+ import {useTransport} from '../../providers/useTransport';
14
+ import {getStudentRouteDetails} from '../../services/transport/studentRouteDetails';
15
+ import type {StudentRouteDetails} from '../../types/transport';
16
+
17
+ export type StudentRouteDetailsScreenProps = {
18
+ renderTop?: () => React.ReactNode;
19
+ };
20
+
21
+ function buildDriverImageUrl(args: {fileBaseUrl?: string; driverImage?: string}) {
22
+ if (!args.fileBaseUrl || !args.driverImage) return undefined;
23
+ const base = args.fileBaseUrl.endsWith('/')
24
+ ? args.fileBaseUrl.slice(0, -1)
25
+ : args.fileBaseUrl;
26
+ const img = args.driverImage.startsWith('/')
27
+ ? args.driverImage.slice(1)
28
+ : args.driverImage;
29
+ return `${base}/staff/document/${img}`;
30
+ }
31
+
32
+ export function StudentRouteDetailsScreen({renderTop}: StudentRouteDetailsScreenProps) {
33
+ const {api, fileBaseUrl} = useTransport();
34
+ const [loading, setLoading] = useState(true);
35
+ const [data, setData] = useState<StudentRouteDetails | null>(null);
36
+ const [loadError, setLoadError] = useState<string | null>(null);
37
+
38
+ const driverImageUrl = useMemo(() => {
39
+ return buildDriverImageUrl({
40
+ fileBaseUrl,
41
+ driverImage: data?.driver_image,
42
+ });
43
+ }, [data?.driver_image, fileBaseUrl]);
44
+
45
+ const load = useCallback(async () => {
46
+ try {
47
+ setLoading(true);
48
+ setLoadError(null);
49
+ const res = await getStudentRouteDetails(api);
50
+ const first = Array.isArray(res.data) ? res.data[0] : undefined;
51
+ setData(first ?? null);
52
+ } catch (e) {
53
+ setData(null);
54
+ setLoadError('Failed to load transport route details');
55
+ } finally {
56
+ setLoading(false);
57
+ }
58
+ }, [api]);
59
+
60
+ useEffect(() => {
61
+ load();
62
+ }, [load]);
63
+
64
+ const handleCall = useCallback(async () => {
65
+ const phone = data?.contact_number;
66
+ if (!phone) {
67
+ Alert.alert('Error', 'Contact number not available');
68
+ return;
69
+ }
70
+
71
+ const phoneNumber = phone.toString().replace(/[^0-9+]/g, '');
72
+ const url = `tel:${phoneNumber}`;
73
+ const supported = await Linking.canOpenURL(url);
74
+ if (!supported) {
75
+ Alert.alert('Error', 'Phone calls are not supported on this device');
76
+ return;
77
+ }
78
+ await Linking.openURL(url);
79
+ }, [data?.contact_number]);
80
+
81
+ if (loading) {
82
+ return (
83
+ <View style={styles.center}>
84
+ <ActivityIndicator size="large" />
85
+ </View>
86
+ );
87
+ }
88
+
89
+ if (!data?.route_title) {
90
+ return (
91
+ <View style={styles.center}>
92
+ {loadError ? (
93
+ <Text style={styles.errorText}>{loadError}</Text>
94
+ ) : (
95
+ <Text style={styles.noDataText}>No Data Found</Text>
96
+ )}
97
+ </View>
98
+ );
99
+ }
100
+
101
+ return (
102
+ <View style={styles.container}>
103
+ {renderTop ? renderTop() : null}
104
+ <ScrollView showsVerticalScrollIndicator={false}>
105
+ <View style={styles.routeTitleCard}>
106
+ <Text style={styles.routeTitle}>{data.route_title}</Text>
107
+ <Text style={styles.routeSubtitle}>Route Details</Text>
108
+ </View>
109
+
110
+ <View style={styles.card}>
111
+ <Text style={styles.cardTitle}>Vehicle Information</Text>
112
+
113
+ <View style={styles.detailRow}>
114
+ <Text style={styles.detailLabel}>Vehicle No:</Text>
115
+ <Text style={styles.detailValue}>{data.vehicle_no ?? '-'}</Text>
116
+ </View>
117
+ <View style={styles.detailRow}>
118
+ <Text style={styles.detailLabel}>Model:</Text>
119
+ <Text style={styles.detailValue}>{data.vehicle_model ?? '-'}</Text>
120
+ </View>
121
+ <View style={styles.detailRow}>
122
+ <Text style={styles.detailLabel}>Stop Name:</Text>
123
+ <Text style={styles.detailValue}>{data.stop_name ?? '-'}</Text>
124
+ </View>
125
+ <View style={styles.detailRow}>
126
+ <Text style={styles.detailLabel}>Pick Up:</Text>
127
+ <Text style={[styles.detailValue, styles.pickup]}>
128
+ {data.pickup_time ?? '-'}
129
+ </Text>
130
+ </View>
131
+ <View style={styles.detailRow}>
132
+ <Text style={styles.detailLabel}>Drop Time:</Text>
133
+ <Text style={[styles.detailValue, styles.drop]}>
134
+ {data.drop_time ?? '-'}
135
+ </Text>
136
+ </View>
137
+ </View>
138
+
139
+ <View style={styles.card}>
140
+ <Text style={styles.cardTitle}>Driver Information</Text>
141
+
142
+ <View style={styles.driverContainer}>
143
+ <View style={styles.driverImageContainer}>
144
+ {driverImageUrl ? (
145
+ <Image style={styles.driverImage} source={{uri: driverImageUrl}} />
146
+ ) : (
147
+ <View style={styles.driverImageFallback} />
148
+ )}
149
+ </View>
150
+
151
+ <View style={styles.driverInfo}>
152
+ <Text style={styles.driverLabel}>Driver Name</Text>
153
+ <Text style={styles.driverName}>{data.driver ?? '-'}</Text>
154
+ </View>
155
+
156
+ <TouchableOpacity onPress={handleCall} style={styles.callButton}>
157
+ <Text style={styles.callText}>Call</Text>
158
+ </TouchableOpacity>
159
+ </View>
160
+ </View>
161
+ </ScrollView>
162
+ </View>
163
+ );
164
+ }
165
+
166
+ const styles = StyleSheet.create({
167
+ container: {
168
+ flex: 1,
169
+ backgroundColor: '#FFFFFF',
170
+ },
171
+ center: {
172
+ flex: 1,
173
+ justifyContent: 'center',
174
+ alignItems: 'center',
175
+ backgroundColor: '#FFFFFF',
176
+ },
177
+ noDataText: {
178
+ fontSize: 16,
179
+ color: '#6B7280',
180
+ },
181
+ errorText: {
182
+ fontSize: 16,
183
+ color: '#DC2626',
184
+ textAlign: 'center',
185
+ paddingHorizontal: 24,
186
+ },
187
+ routeTitleCard: {
188
+ margin: 16,
189
+ borderRadius: 16,
190
+ padding: 16,
191
+ backgroundColor: '#F3F4F6',
192
+ alignItems: 'center',
193
+ },
194
+ routeTitle: {
195
+ fontSize: 18,
196
+ fontWeight: '700',
197
+ color: '#111827',
198
+ },
199
+ routeSubtitle: {
200
+ marginTop: 6,
201
+ fontSize: 12,
202
+ color: '#6B7280',
203
+ },
204
+ card: {
205
+ marginHorizontal: 16,
206
+ marginBottom: 16,
207
+ borderRadius: 16,
208
+ padding: 16,
209
+ backgroundColor: '#FFFFFF',
210
+ borderWidth: 1,
211
+ borderColor: '#E5E7EB',
212
+ },
213
+ cardTitle: {
214
+ fontSize: 16,
215
+ fontWeight: '700',
216
+ color: '#111827',
217
+ marginBottom: 12,
218
+ },
219
+ detailRow: {
220
+ flexDirection: 'row',
221
+ justifyContent: 'space-between',
222
+ paddingVertical: 6,
223
+ },
224
+ detailLabel: {
225
+ fontSize: 14,
226
+ color: '#374151',
227
+ },
228
+ detailValue: {
229
+ fontSize: 14,
230
+ color: '#111827',
231
+ fontWeight: '600',
232
+ },
233
+ pickup: {
234
+ color: '#16A34A',
235
+ },
236
+ drop: {
237
+ color: '#DC2626',
238
+ },
239
+ driverContainer: {
240
+ flexDirection: 'row',
241
+ alignItems: 'center',
242
+ },
243
+ driverImageContainer: {
244
+ width: 56,
245
+ height: 56,
246
+ borderRadius: 28,
247
+ overflow: 'hidden',
248
+ backgroundColor: '#F3F4F6',
249
+ },
250
+ driverImage: {
251
+ width: 56,
252
+ height: 56,
253
+ },
254
+ driverImageFallback: {
255
+ width: 56,
256
+ height: 56,
257
+ backgroundColor: '#E5E7EB',
258
+ },
259
+ driverInfo: {
260
+ flex: 1,
261
+ marginLeft: 12,
262
+ },
263
+ driverLabel: {
264
+ fontSize: 12,
265
+ color: '#6B7280',
266
+ },
267
+ driverName: {
268
+ fontSize: 14,
269
+ fontWeight: '700',
270
+ color: '#111827',
271
+ marginTop: 2,
272
+ },
273
+ callButton: {
274
+ paddingHorizontal: 12,
275
+ paddingVertical: 8,
276
+ borderRadius: 10,
277
+ backgroundColor: '#DCFCE7',
278
+ },
279
+ callText: {
280
+ color: '#16A34A',
281
+ fontWeight: '700',
282
+ },
283
+ });
284
+
@@ -0,0 +1,93 @@
1
+ import React, {useMemo} from 'react';
2
+ import {
3
+ FlatList,
4
+ Pressable,
5
+ StyleSheet,
6
+ Text,
7
+ View,
8
+ type ListRenderItem,
9
+ } from 'react-native';
10
+
11
+ export type TransportDashboardItem = {
12
+ id: string;
13
+ title: string;
14
+ onPress: () => void;
15
+ icon?: React.ReactNode;
16
+ };
17
+
18
+ export type TransportDashboardProps = {
19
+ items?: TransportDashboardItem[];
20
+ };
21
+
22
+ export function TransportDashboard({items}: TransportDashboardProps) {
23
+ const data = useMemo(() => items ?? [], [items]);
24
+
25
+ const renderItem: ListRenderItem<TransportDashboardItem> = ({item}) => {
26
+ return (
27
+ <Pressable onPress={item.onPress} style={styles.card}>
28
+ <View style={styles.iconContainer}>{item.icon ?? null}</View>
29
+ <Text style={styles.title}>{item.title}</Text>
30
+ </Pressable>
31
+ );
32
+ };
33
+
34
+ return (
35
+ <View style={styles.container}>
36
+ <FlatList
37
+ data={data}
38
+ renderItem={renderItem}
39
+ numColumns={2}
40
+ contentContainerStyle={styles.listContainer}
41
+ keyExtractor={item => item.id}
42
+ />
43
+ </View>
44
+ );
45
+ }
46
+
47
+ const styles = StyleSheet.create({
48
+ container: {
49
+ flex: 1,
50
+ backgroundColor: '#FFFFFF',
51
+ },
52
+ listContainer: {
53
+ padding: 16,
54
+ paddingTop: 30,
55
+ },
56
+ card: {
57
+ flex: 1,
58
+ backgroundColor: '#FFFFFF',
59
+ margin: 10,
60
+ borderRadius: 20,
61
+ paddingVertical: 24,
62
+ paddingHorizontal: 16,
63
+ alignItems: 'center',
64
+ justifyContent: 'center',
65
+ shadowColor: '#000',
66
+ shadowOffset: {
67
+ width: 0,
68
+ height: 4,
69
+ },
70
+ shadowOpacity: 0.1,
71
+ shadowRadius: 8,
72
+ elevation: 5,
73
+ borderWidth: 1,
74
+ borderColor: '#F0F0F0',
75
+ },
76
+ iconContainer: {
77
+ width: 80,
78
+ height: 80,
79
+ backgroundColor: '#E3F2FD',
80
+ borderRadius: 40,
81
+ justifyContent: 'center',
82
+ alignItems: 'center',
83
+ marginBottom: 16,
84
+ },
85
+ title: {
86
+ fontSize: 16,
87
+ fontWeight: '600',
88
+ color: '#111827',
89
+ textAlign: 'center',
90
+ letterSpacing: 0.5,
91
+ },
92
+ });
93
+
@@ -0,0 +1,170 @@
1
+ import axios from 'axios';
2
+ import type {AxiosError, AxiosRequestConfig} from 'axios';
3
+ import type {ApiClient, ApiError} from './types';
4
+ import type {
5
+ GetAuthToken,
6
+ RefreshAuthToken,
7
+ TransportHeaders,
8
+ } from '../../providers/types';
9
+
10
+ type CreateApiClientArgs = {
11
+ baseUrl: string;
12
+ authToken?: string;
13
+ getAuthToken?: GetAuthToken;
14
+ headers?: TransportHeaders;
15
+ refreshAuthToken?: RefreshAuthToken;
16
+ onApiError?: (error: ApiError) => void;
17
+ retry?: {
18
+ retries?: number;
19
+ retryDelayMs?: number;
20
+ retryOnStatuses?: number[];
21
+ };
22
+ };
23
+
24
+ type InternalAxiosRequestConfig = AxiosRequestConfig & {
25
+ __retryCount?: number;
26
+ __isRetryAfterRefresh?: boolean;
27
+ };
28
+
29
+ function normalizeAxiosError(err: AxiosError): ApiError {
30
+ return {
31
+ message: err.message,
32
+ status: err.response?.status,
33
+ code: err.code,
34
+ url: err.config?.url,
35
+ method: err.config?.method,
36
+ data: err.response?.data,
37
+ raw: err,
38
+ };
39
+ }
40
+
41
+ async function resolveToken(args: {
42
+ authToken?: string;
43
+ getAuthToken?: GetAuthToken;
44
+ }) {
45
+ if (args.authToken) return args.authToken;
46
+ if (!args.getAuthToken) return undefined;
47
+ const token = await args.getAuthToken();
48
+ return token ?? undefined;
49
+ }
50
+
51
+ function delay(ms: number) {
52
+ return new Promise<void>(resolve => setTimeout(() => resolve(), ms));
53
+ }
54
+
55
+ export function createApiClient(args: CreateApiClientArgs): ApiClient {
56
+ const baseUrl = args.baseUrl.endsWith('/')
57
+ ? args.baseUrl.slice(0, -1)
58
+ : args.baseUrl;
59
+ const instance = axios.create({
60
+ baseURL: baseUrl,
61
+ });
62
+
63
+ const retryConfig = {
64
+ retries: args.retry?.retries ?? 1,
65
+ retryDelayMs: args.retry?.retryDelayMs ?? 500,
66
+ retryOnStatuses: args.retry?.retryOnStatuses ?? [408, 429, 500, 502, 503, 504],
67
+ };
68
+
69
+ let refreshPromise: Promise<string | null | undefined> | null = null;
70
+
71
+ instance.interceptors.request.use(async (config: any) => {
72
+ const headers: Record<string, string> = {
73
+ ...(args.headers ?? {}),
74
+ ...(config.headers as Record<string, string> | undefined),
75
+ };
76
+
77
+ const hasAuthorizationHeader =
78
+ typeof headers.Authorization === 'string' && headers.Authorization.length > 0;
79
+
80
+ if (!hasAuthorizationHeader) {
81
+ const token = await resolveToken({
82
+ authToken: args.authToken,
83
+ getAuthToken: args.getAuthToken,
84
+ });
85
+ if (token) {
86
+ headers.Authorization = token;
87
+ }
88
+ }
89
+
90
+ config.headers = headers as any;
91
+ return config;
92
+ });
93
+
94
+ instance.interceptors.response.use(
95
+ res => res,
96
+ async (error: AxiosError) => {
97
+ const normalized = normalizeAxiosError(error);
98
+ const config = (error.config ?? {}) as InternalAxiosRequestConfig;
99
+ const status = error.response?.status;
100
+
101
+ const shouldRefresh =
102
+ status === 401 && !!args.refreshAuthToken && !config.__isRetryAfterRefresh;
103
+
104
+ if (shouldRefresh && args.refreshAuthToken) {
105
+ const currentToken = await resolveToken({
106
+ authToken: args.authToken,
107
+ getAuthToken: args.getAuthToken,
108
+ });
109
+
110
+ if (!refreshPromise) {
111
+ refreshPromise = args.refreshAuthToken({
112
+ failedRequest: {
113
+ url: config.url,
114
+ method: config.method,
115
+ status,
116
+ },
117
+ currentToken: currentToken ?? null,
118
+ }).finally(() => {
119
+ refreshPromise = null;
120
+ });
121
+ }
122
+
123
+ const newToken = await refreshPromise;
124
+ if (newToken) {
125
+ config.__isRetryAfterRefresh = true;
126
+ config.headers = {
127
+ ...(config.headers as Record<string, string> | undefined),
128
+ Authorization: newToken,
129
+ };
130
+ return instance.request(config);
131
+ }
132
+ }
133
+
134
+ const shouldRetry =
135
+ (status && retryConfig.retryOnStatuses.includes(status)) ||
136
+ (!status && Boolean(error.code));
137
+
138
+ if (shouldRetry) {
139
+ config.__retryCount = (config.__retryCount ?? 0) + 1;
140
+ if (config.__retryCount <= retryConfig.retries) {
141
+ await delay(retryConfig.retryDelayMs);
142
+ return instance.request(config);
143
+ }
144
+ }
145
+
146
+ args.onApiError?.(normalized);
147
+ return Promise.reject(error);
148
+ },
149
+ );
150
+
151
+ return {
152
+ instance,
153
+ request<T = unknown>(config: AxiosRequestConfig) {
154
+ return instance.request<T>(config);
155
+ },
156
+ async get<T = unknown>(url: string, config?: AxiosRequestConfig) {
157
+ const res = await instance.get<T>(url, config);
158
+ return res.data;
159
+ },
160
+ async post<T = unknown>(
161
+ url: string,
162
+ data?: unknown,
163
+ config?: AxiosRequestConfig,
164
+ ) {
165
+ const res = await instance.post<T>(url, data, config);
166
+ return res.data;
167
+ },
168
+ };
169
+ }
170
+
@@ -0,0 +1,30 @@
1
+ import type {
2
+ AxiosError,
3
+ AxiosInstance,
4
+ AxiosRequestConfig,
5
+ AxiosResponse,
6
+ } from 'axios';
7
+
8
+ export type ApiRequestConfig = AxiosRequestConfig;
9
+
10
+ export type ApiError = {
11
+ message: string;
12
+ status?: number;
13
+ code?: string;
14
+ url?: string;
15
+ method?: string;
16
+ data?: unknown;
17
+ raw?: AxiosError;
18
+ };
19
+
20
+ export type ApiClient = {
21
+ instance: AxiosInstance;
22
+ request<T = unknown>(config: ApiRequestConfig): Promise<AxiosResponse<T>>;
23
+ get<T = unknown>(url: string, config?: ApiRequestConfig): Promise<T>;
24
+ post<T = unknown>(
25
+ url: string,
26
+ data?: unknown,
27
+ config?: ApiRequestConfig,
28
+ ): Promise<T>;
29
+ };
30
+
@@ -0,0 +1,2 @@
1
+ export {};
2
+
@@ -0,0 +1,10 @@
1
+ import type {ApiClient} from '../api/types';
2
+ import type {StudentRouteDetails} from '../../types/transport';
3
+ import type {ApiEnvelope} from './types';
4
+
5
+ export async function getStudentRouteDetails(api: ApiClient) {
6
+ return api.get<ApiEnvelope<StudentRouteDetails[]>>(
7
+ '/student/studentAPP/TransportDetails/studentRoutedetails',
8
+ );
9
+ }
10
+
@@ -0,0 +1,7 @@
1
+ export type ApiEnvelope<T> = {
2
+ Status?: string;
3
+ Message?: string;
4
+ message?: string;
5
+ data?: T;
6
+ };
7
+
@@ -0,0 +1,2 @@
1
+ export {};
2
+
@@ -0,0 +1,2 @@
1
+ export type {StudentRouteDetails} from './transport';
2
+
@@ -0,0 +1,12 @@
1
+ export type StudentRouteDetails = {
2
+ route_title?: string;
3
+ vehicle_no?: string;
4
+ vehicle_model?: string;
5
+ stop_name?: string;
6
+ pickup_time?: string;
7
+ drop_time?: string;
8
+ driver?: string;
9
+ driver_image?: string;
10
+ contact_number?: string | number;
11
+ };
12
+
@@ -0,0 +1,2 @@
1
+ export {};
2
+