vorqard-ai-sdk 1.0.4 → 1.0.6
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/package.json +3 -7
- package/src/components/ChatComponents.js +4 -4
- package/src/components/UICards.js +458 -98
- package/src/components/VoiceInputPanel.js +180 -80
- package/src/hooks/useRealtimeVoice.js +35 -7
- package/src/screens/AIHealthChatScreen.js +34 -6
- package/src/screens/AIVoiceAssistantScreen.js +12 -164
- package/src/hooks/useVoiceChat.js +0 -373
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vorqard-ai-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Standalone React Native SDK for VORQARD AI Health Assistant and Report Analyzer",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"react-native-linear-gradient": "*",
|
|
20
20
|
"react-native-safe-area-context": "*",
|
|
21
21
|
"react-native-vector-icons": "*",
|
|
22
|
-
"react-native-vision-camera": "*",
|
|
23
22
|
"react-native-webrtc": "*"
|
|
24
23
|
},
|
|
25
24
|
"keywords": [
|
|
@@ -30,8 +29,5 @@
|
|
|
30
29
|
"healthcare"
|
|
31
30
|
],
|
|
32
31
|
"author": "VORQARD Developer",
|
|
33
|
-
"license": "Abhivorn Technologies"
|
|
34
|
-
|
|
35
|
-
"react-native-live-audio-stream": "^1.1.1"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
32
|
+
"license": "Abhivorn Technologies"
|
|
33
|
+
}
|
|
@@ -233,11 +233,11 @@ export const InputPanel = ({ value = '', onChangeText, onSend, onVoicePress, loa
|
|
|
233
233
|
</TouchableOpacity>
|
|
234
234
|
)}
|
|
235
235
|
|
|
236
|
-
{!hasText && (
|
|
237
|
-
<
|
|
236
|
+
{/* {!hasText && (
|
|
237
|
+
<View style={styles.iconBtn}>
|
|
238
238
|
<Ionicons name="mic" size={20} color="#94A3B8" />
|
|
239
|
-
</
|
|
240
|
-
)}
|
|
239
|
+
</View>
|
|
240
|
+
)} */}
|
|
241
241
|
|
|
242
242
|
<TouchableOpacity
|
|
243
243
|
onPress={() => onSend(value)}
|
|
@@ -1,94 +1,367 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, Image, Linking, ScrollView } from 'react-native';
|
|
3
3
|
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
4
4
|
|
|
5
|
+
// Rich card components (already built for the chat UI, reused here)
|
|
6
|
+
import { SpecialtySelectionCard } from './cards/SpecialtySelectionCard';
|
|
7
|
+
import { DoctorSelectionList } from './cards/DoctorCards';
|
|
8
|
+
import { SlotSelectionCard } from './cards/SlotSelectionCard';
|
|
9
|
+
import { BookingConfirmationCard } from './cards/BookingConfirmationCard';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* UICardContainer — renders the backend-driven ui_action payload.
|
|
13
|
+
*
|
|
14
|
+
* Interaction text conventions (sent via sendUIInteraction → DataChannel):
|
|
15
|
+
* specialty_cards → opt.id e.g. "Cardiology"
|
|
16
|
+
* doctor_cards → "doctor:<id>" e.g. "doctor:5"
|
|
17
|
+
* provider_selection → "doctor:<id>" VORQARD tap
|
|
18
|
+
* → "nearby:<id>" Nearby tap
|
|
19
|
+
* nearby_details → navigation via Linking (no DataChannel)
|
|
20
|
+
* slot_cards (slot) → opt.label e.g. "10:30"
|
|
21
|
+
* slot_cards (date) → "date:YYYY-MM-DD" from date picker
|
|
22
|
+
* payment_card → opt.id "cash" | "upi" | "card"
|
|
23
|
+
* booking_success → no interaction
|
|
24
|
+
*/
|
|
5
25
|
export const UICardContainer = ({ action, onAction }) => {
|
|
6
26
|
if (!action) return null;
|
|
7
27
|
|
|
8
28
|
switch (action.type) {
|
|
29
|
+
|
|
30
|
+
// ── Phase 3/4: existing cards ─────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
case 'specialty_cards':
|
|
33
|
+
return (
|
|
34
|
+
<SpecialtySelectionCard
|
|
35
|
+
options={action.items}
|
|
36
|
+
onOptionPress={(opt) => onAction(opt.id)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
case 'doctor_cards':
|
|
41
|
+
return (
|
|
42
|
+
<DoctorSelectionList
|
|
43
|
+
options={action.items}
|
|
44
|
+
onOptionPress={(opt) => onAction(`doctor:${opt.id}`)}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
case 'slot_cards':
|
|
49
|
+
return (
|
|
50
|
+
<SlotSelectionCard
|
|
51
|
+
doctor={action.doctor}
|
|
52
|
+
selectedDate={action.date}
|
|
53
|
+
options={action.items}
|
|
54
|
+
onOptionPress={(opt) => onAction(opt.label)}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
case 'confirmation_card':
|
|
59
|
+
return (
|
|
60
|
+
<BookingConfirmationCard
|
|
61
|
+
ui={{
|
|
62
|
+
doctor: action.doctor,
|
|
63
|
+
date: action.date,
|
|
64
|
+
slot: action.slot,
|
|
65
|
+
options: action.items,
|
|
66
|
+
}}
|
|
67
|
+
onOptionPress={(opt) => onAction(opt.id)}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
case 'booking_success':
|
|
72
|
+
return <BookingSuccessCard action={action} />;
|
|
73
|
+
|
|
74
|
+
// ── Phase 5: two-category provider selection ──────────────────────────────
|
|
75
|
+
|
|
76
|
+
case 'provider_selection':
|
|
77
|
+
return (
|
|
78
|
+
<ProviderSelectionCard
|
|
79
|
+
action={action}
|
|
80
|
+
onVorqardPress={(doc) => onAction(`doctor:${doc.id}`)}
|
|
81
|
+
onNearbyPress={(p) => onAction(`nearby:${p.id}`)}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
case 'nearby_details':
|
|
86
|
+
return <NearbyDetailsCard action={action} />;
|
|
87
|
+
|
|
88
|
+
// ── Phase 6: payment ──────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
case 'payment_card':
|
|
91
|
+
return (
|
|
92
|
+
<PaymentCard
|
|
93
|
+
action={action}
|
|
94
|
+
onMethodPress={(opt) => onAction(opt.id)}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// ── Legacy / Gemini-path cards (backwards compat) ─────────────────────────
|
|
99
|
+
|
|
9
100
|
case 'selection':
|
|
10
|
-
return <
|
|
101
|
+
return <LegacySelectionCard action={action} onAction={onAction} />;
|
|
102
|
+
|
|
11
103
|
case 'slot_selection':
|
|
12
|
-
return <
|
|
13
|
-
|
|
14
|
-
return <BookingSuccessCard action={action} onAction={onAction} />;
|
|
104
|
+
return <LegacySlotCard action={action} onAction={onAction} />;
|
|
105
|
+
|
|
15
106
|
case 'appointments_list':
|
|
16
107
|
return <AppointmentsListCard action={action} onAction={onAction} />;
|
|
108
|
+
|
|
17
109
|
default:
|
|
18
110
|
return null;
|
|
19
111
|
}
|
|
20
112
|
};
|
|
21
113
|
|
|
22
|
-
|
|
114
|
+
// ── Booking success (shared by both paths) ────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
const BookingSuccessCard = ({ action }) => (
|
|
117
|
+
<View style={styles.successCard}>
|
|
118
|
+
<View style={styles.successIcon}>
|
|
119
|
+
<Ionicons name="checkmark-circle" size={48} color="#10B981" />
|
|
120
|
+
</View>
|
|
121
|
+
<Text style={styles.successTitle}>Booking Confirmed!</Text>
|
|
122
|
+
<View style={styles.detailRow}>
|
|
123
|
+
<Ionicons name="person-outline" size={16} color="rgba(255,255,255,0.7)" />
|
|
124
|
+
<Text style={styles.detailText}>{action.doctor}</Text>
|
|
125
|
+
</View>
|
|
126
|
+
<View style={styles.detailRow}>
|
|
127
|
+
<Ionicons name="calendar-outline" size={16} color="rgba(255,255,255,0.7)" />
|
|
128
|
+
<Text style={styles.detailText}>{action.date} at {action.slot}</Text>
|
|
129
|
+
</View>
|
|
130
|
+
</View>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// ── Phase 5: Provider Selection (Our Services + Nearby) ──────────────────────
|
|
134
|
+
|
|
135
|
+
const ProviderSelectionCard = ({ action, onVorqardPress, onNearbyPress }) => {
|
|
136
|
+
const [tab, setTab] = useState('our_services');
|
|
137
|
+
const vorqardList = action.our_services || [];
|
|
138
|
+
const nearbyList = action.nearby || [];
|
|
139
|
+
|
|
23
140
|
return (
|
|
24
141
|
<View style={styles.card}>
|
|
25
|
-
<Text style={styles.title}>{action.title || '
|
|
26
|
-
|
|
142
|
+
<Text style={styles.title}>{action.title || 'Find a Provider'}</Text>
|
|
143
|
+
|
|
144
|
+
{/* Tab strip */}
|
|
145
|
+
<View style={styles.tabRow}>
|
|
27
146
|
<TouchableOpacity
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
onPress={() => onAction(opt.label || opt.name)}
|
|
147
|
+
style={[styles.tab, tab === 'our_services' && styles.tabActive]}
|
|
148
|
+
onPress={() => setTab('our_services')}
|
|
31
149
|
>
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<Ionicons name="person" size={20} color="#fff" />
|
|
37
|
-
</View>
|
|
38
|
-
)}
|
|
39
|
-
<View style={styles.docInfo}>
|
|
40
|
-
<Text style={styles.docName}>{opt.name || opt.label}</Text>
|
|
41
|
-
{opt.specialty && <Text style={styles.docSpec}>{opt.specialty}</Text>}
|
|
42
|
-
<View style={styles.docMetaRow}>
|
|
43
|
-
{opt.experience && <Text style={styles.docMeta}>{opt.experience} yrs exp</Text>}
|
|
44
|
-
{opt.consultation_fee && <Text style={styles.docMeta}> • ₹{opt.consultation_fee}</Text>}
|
|
45
|
-
</View>
|
|
46
|
-
</View>
|
|
150
|
+
<Ionicons name="shield-checkmark-outline" size={14} color={tab === 'our_services' ? '#818CF8' : 'rgba(255,255,255,0.5)'} />
|
|
151
|
+
<Text style={[styles.tabText, tab === 'our_services' && styles.tabTextActive]}>
|
|
152
|
+
Our Services ({vorqardList.length})
|
|
153
|
+
</Text>
|
|
47
154
|
</TouchableOpacity>
|
|
48
|
-
|
|
155
|
+
<TouchableOpacity
|
|
156
|
+
style={[styles.tab, tab === 'nearby' && styles.tabActive]}
|
|
157
|
+
onPress={() => setTab('nearby')}
|
|
158
|
+
>
|
|
159
|
+
<Ionicons name="location-outline" size={14} color={tab === 'nearby' ? '#818CF8' : 'rgba(255,255,255,0.5)'} />
|
|
160
|
+
<Text style={[styles.tabText, tab === 'nearby' && styles.tabTextActive]}>
|
|
161
|
+
Nearby ({nearbyList.length})
|
|
162
|
+
</Text>
|
|
163
|
+
</TouchableOpacity>
|
|
164
|
+
</View>
|
|
165
|
+
|
|
166
|
+
{tab === 'our_services' ? (
|
|
167
|
+
vorqardList.length === 0 ? (
|
|
168
|
+
<Text style={styles.emptyText}>No VORQARD specialists found in this area.</Text>
|
|
169
|
+
) : (
|
|
170
|
+
vorqardList.map((doc, i) => (
|
|
171
|
+
<TouchableOpacity key={doc.id || i} style={styles.doctorItem} onPress={() => onVorqardPress(doc)}>
|
|
172
|
+
{doc.profile_picture
|
|
173
|
+
? <Image source={{ uri: doc.profile_picture }} style={styles.docImg} />
|
|
174
|
+
: <View style={styles.docAvatar}><Ionicons name="person" size={20} color="#fff" /></View>
|
|
175
|
+
}
|
|
176
|
+
<View style={styles.docInfo}>
|
|
177
|
+
<Text style={styles.docName}>{doc.name}</Text>
|
|
178
|
+
<Text style={styles.docSpec}>{doc.specialty}</Text>
|
|
179
|
+
<View style={styles.docMetaRow}>
|
|
180
|
+
{doc.consultation_fee > 0 && <Text style={styles.docMeta}>₹{doc.consultation_fee}</Text>}
|
|
181
|
+
{doc.distance != null && <Text style={styles.docMeta}> • {doc.distance} km</Text>}
|
|
182
|
+
{doc.rating > 0 && <Text style={styles.docMeta}> • ⭐ {doc.rating}</Text>}
|
|
183
|
+
</View>
|
|
184
|
+
</View>
|
|
185
|
+
<View style={styles.bookBadge}>
|
|
186
|
+
<Text style={styles.bookBadgeText}>Book</Text>
|
|
187
|
+
</View>
|
|
188
|
+
</TouchableOpacity>
|
|
189
|
+
))
|
|
190
|
+
)
|
|
191
|
+
) : (
|
|
192
|
+
nearbyList.length === 0 ? (
|
|
193
|
+
<Text style={styles.emptyText}>No nearby clinics found in this area.</Text>
|
|
194
|
+
) : (
|
|
195
|
+
nearbyList.map((p, i) => (
|
|
196
|
+
<TouchableOpacity key={p.id || i} style={styles.nearbyItem} onPress={() => onNearbyPress(p)}>
|
|
197
|
+
<View style={styles.nearbyIcon}>
|
|
198
|
+
<Ionicons name="business-outline" size={22} color="#818CF8" />
|
|
199
|
+
</View>
|
|
200
|
+
<View style={styles.docInfo}>
|
|
201
|
+
<Text style={styles.docName}>{p.name}</Text>
|
|
202
|
+
{p.address ? <Text style={styles.docSpec} numberOfLines={1}>{p.address}</Text> : null}
|
|
203
|
+
<View style={styles.docMetaRow}>
|
|
204
|
+
{p.distance != null && <Text style={styles.docMeta}>{p.distance} km away</Text>}
|
|
205
|
+
{p.phone ? <Text style={styles.docMeta}> • {p.phone}</Text> : null}
|
|
206
|
+
</View>
|
|
207
|
+
</View>
|
|
208
|
+
<Ionicons name="chevron-forward" size={16} color="rgba(255,255,255,0.4)" />
|
|
209
|
+
</TouchableOpacity>
|
|
210
|
+
))
|
|
211
|
+
)
|
|
212
|
+
)}
|
|
49
213
|
</View>
|
|
50
214
|
);
|
|
51
215
|
};
|
|
52
216
|
|
|
53
|
-
|
|
217
|
+
// ── Phase 5: Nearby Details (info-only, no booking) ───────────────────────────
|
|
218
|
+
|
|
219
|
+
const NearbyDetailsCard = ({ action }) => {
|
|
220
|
+
const p = action.provider || {};
|
|
221
|
+
const openMaps = () => {
|
|
222
|
+
if (p.maps_url) Linking.openURL(p.maps_url).catch(() => {});
|
|
223
|
+
};
|
|
224
|
+
const callPhone = () => {
|
|
225
|
+
if (p.phone) Linking.openURL(`tel:${p.phone}`).catch(() => {});
|
|
226
|
+
};
|
|
227
|
+
|
|
54
228
|
return (
|
|
55
|
-
<View style={styles.card}>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
>
|
|
65
|
-
|
|
229
|
+
<View style={[styles.card, styles.nearbyDetailCard]}>
|
|
230
|
+
<View style={styles.nearbyDetailHeader}>
|
|
231
|
+
<Ionicons name="business" size={28} color="#818CF8" />
|
|
232
|
+
<Text style={[styles.title, { marginLeft: 10 }]}>{p.name || 'Nearby Clinic'}</Text>
|
|
233
|
+
</View>
|
|
234
|
+
|
|
235
|
+
{p.address ? (
|
|
236
|
+
<View style={styles.detailRow}>
|
|
237
|
+
<Ionicons name="location-outline" size={16} color="rgba(255,255,255,0.6)" />
|
|
238
|
+
<Text style={styles.detailText}>{p.address}</Text>
|
|
239
|
+
</View>
|
|
240
|
+
) : null}
|
|
241
|
+
|
|
242
|
+
{p.distance != null ? (
|
|
243
|
+
<View style={styles.detailRow}>
|
|
244
|
+
<Ionicons name="navigate-outline" size={16} color="rgba(255,255,255,0.6)" />
|
|
245
|
+
<Text style={styles.detailText}>{p.distance} km away</Text>
|
|
246
|
+
</View>
|
|
247
|
+
) : null}
|
|
248
|
+
|
|
249
|
+
{p.phone ? (
|
|
250
|
+
<View style={styles.detailRow}>
|
|
251
|
+
<Ionicons name="call-outline" size={16} color="rgba(255,255,255,0.6)" />
|
|
252
|
+
<Text style={styles.detailText}>{p.phone}</Text>
|
|
253
|
+
</View>
|
|
254
|
+
) : null}
|
|
255
|
+
|
|
256
|
+
<View style={styles.nearbyBtnRow}>
|
|
257
|
+
{p.phone ? (
|
|
258
|
+
<TouchableOpacity style={styles.nearbyBtn} onPress={callPhone}>
|
|
259
|
+
<Ionicons name="call" size={16} color="#fff" />
|
|
260
|
+
<Text style={styles.nearbyBtnText}>Call</Text>
|
|
66
261
|
</TouchableOpacity>
|
|
67
|
-
)
|
|
262
|
+
) : null}
|
|
263
|
+
{p.maps_url ? (
|
|
264
|
+
<TouchableOpacity style={[styles.nearbyBtn, styles.navBtn]} onPress={openMaps}>
|
|
265
|
+
<Ionicons name="navigate" size={16} color="#fff" />
|
|
266
|
+
<Text style={styles.nearbyBtnText}>Navigate</Text>
|
|
267
|
+
</TouchableOpacity>
|
|
268
|
+
) : null}
|
|
269
|
+
</View>
|
|
270
|
+
|
|
271
|
+
<View style={styles.nearbyNoBookNotice}>
|
|
272
|
+
<Ionicons name="information-circle-outline" size={14} color="rgba(255,255,255,0.4)" />
|
|
273
|
+
<Text style={styles.nearbyNoBookText}>Booking not available — visit or call directly.</Text>
|
|
68
274
|
</View>
|
|
69
275
|
</View>
|
|
70
276
|
);
|
|
71
277
|
};
|
|
72
278
|
|
|
73
|
-
|
|
279
|
+
// ── Phase 6: Payment Card ─────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
const PaymentCard = ({ action, onMethodPress }) => {
|
|
282
|
+
const doc = action.doctor || {};
|
|
74
283
|
return (
|
|
75
|
-
<View style={styles.
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
284
|
+
<View style={[styles.card, styles.paymentCard]}>
|
|
285
|
+
<Text style={styles.title}>{action.title || 'Payment'}</Text>
|
|
286
|
+
|
|
287
|
+
{/* Appointment summary */}
|
|
288
|
+
<View style={styles.paymentSummary}>
|
|
289
|
+
<View style={styles.detailRow}>
|
|
290
|
+
<Ionicons name="person-outline" size={15} color="rgba(255,255,255,0.6)" />
|
|
291
|
+
<Text style={styles.detailText}>{doc.name || 'Doctor'}</Text>
|
|
292
|
+
</View>
|
|
293
|
+
<View style={styles.detailRow}>
|
|
294
|
+
<Ionicons name="calendar-outline" size={15} color="rgba(255,255,255,0.6)" />
|
|
295
|
+
<Text style={styles.detailText}>{action.date} at {action.slot}</Text>
|
|
296
|
+
</View>
|
|
297
|
+
<View style={[styles.detailRow, styles.amountRow]}>
|
|
298
|
+
<Ionicons name="cash-outline" size={15} color="#10B981" />
|
|
299
|
+
<Text style={styles.amountText}>₹{action.amount || 0}</Text>
|
|
300
|
+
</View>
|
|
87
301
|
</View>
|
|
302
|
+
|
|
303
|
+
{/* Payment options */}
|
|
304
|
+
<Text style={styles.subtitle}>Choose payment method</Text>
|
|
305
|
+
{(action.items || []).map((opt, i) => (
|
|
306
|
+
<TouchableOpacity key={opt.id || i} style={styles.paymentBtn} onPress={() => onMethodPress(opt)}>
|
|
307
|
+
<Ionicons name={opt.icon || 'card-outline'} size={20} color="#818CF8" />
|
|
308
|
+
<Text style={styles.paymentBtnText}>{opt.label}</Text>
|
|
309
|
+
<Ionicons name="chevron-forward" size={16} color="rgba(255,255,255,0.4)" />
|
|
310
|
+
</TouchableOpacity>
|
|
311
|
+
))}
|
|
88
312
|
</View>
|
|
89
313
|
);
|
|
90
314
|
};
|
|
91
315
|
|
|
316
|
+
// ── Legacy cards (kept for Gemini-routed queries) ─────────────────────────────
|
|
317
|
+
|
|
318
|
+
const LegacySelectionCard = ({ action, onAction }) => (
|
|
319
|
+
<View style={styles.card}>
|
|
320
|
+
<Text style={styles.title}>{action.title || 'Please Select'}</Text>
|
|
321
|
+
{action.options?.map((opt, i) => (
|
|
322
|
+
<TouchableOpacity
|
|
323
|
+
key={opt.id || i}
|
|
324
|
+
style={styles.doctorItem}
|
|
325
|
+
onPress={() => onAction(opt.label || opt.name)}
|
|
326
|
+
>
|
|
327
|
+
{opt.profile_picture ? (
|
|
328
|
+
<Image source={{ uri: opt.profile_picture }} style={styles.docImg} />
|
|
329
|
+
) : (
|
|
330
|
+
<View style={styles.docAvatar}>
|
|
331
|
+
<Ionicons name="person" size={20} color="#fff" />
|
|
332
|
+
</View>
|
|
333
|
+
)}
|
|
334
|
+
<View style={styles.docInfo}>
|
|
335
|
+
<Text style={styles.docName}>{opt.name || opt.label}</Text>
|
|
336
|
+
{opt.specialty && <Text style={styles.docSpec}>{opt.specialty}</Text>}
|
|
337
|
+
<View style={styles.docMetaRow}>
|
|
338
|
+
{opt.experience && <Text style={styles.docMeta}>{opt.experience} yrs exp</Text>}
|
|
339
|
+
{opt.consultation_fee && <Text style={styles.docMeta}> • ₹{opt.consultation_fee}</Text>}
|
|
340
|
+
</View>
|
|
341
|
+
</View>
|
|
342
|
+
</TouchableOpacity>
|
|
343
|
+
))}
|
|
344
|
+
</View>
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const LegacySlotCard = ({ action, onAction }) => (
|
|
348
|
+
<View style={styles.card}>
|
|
349
|
+
<Text style={styles.title}>{action.title || 'Select Time'}</Text>
|
|
350
|
+
<Text style={styles.subtitle}>{action.selected_date}</Text>
|
|
351
|
+
<View style={styles.slotGrid}>
|
|
352
|
+
{action.options?.map((opt, i) => (
|
|
353
|
+
<TouchableOpacity
|
|
354
|
+
key={opt.id || i}
|
|
355
|
+
style={styles.slotBtn}
|
|
356
|
+
onPress={() => onAction(opt.label)}
|
|
357
|
+
>
|
|
358
|
+
<Text style={styles.slotText}>{opt.label}</Text>
|
|
359
|
+
</TouchableOpacity>
|
|
360
|
+
))}
|
|
361
|
+
</View>
|
|
362
|
+
</View>
|
|
363
|
+
);
|
|
364
|
+
|
|
92
365
|
const AppointmentsListCard = ({ action, onAction }) => {
|
|
93
366
|
if (!action.appointments || action.appointments.length === 0) {
|
|
94
367
|
return (
|
|
@@ -115,6 +388,8 @@ const AppointmentsListCard = ({ action, onAction }) => {
|
|
|
115
388
|
);
|
|
116
389
|
};
|
|
117
390
|
|
|
391
|
+
// ── Styles ────────────────────────────────────────────────────────────────────
|
|
392
|
+
|
|
118
393
|
const styles = StyleSheet.create({
|
|
119
394
|
card: {
|
|
120
395
|
backgroundColor: 'rgba(255,255,255,0.08)',
|
|
@@ -160,32 +435,12 @@ const styles = StyleSheet.create({
|
|
|
160
435
|
alignItems: 'center',
|
|
161
436
|
marginRight: 12,
|
|
162
437
|
},
|
|
163
|
-
docInfo: {
|
|
164
|
-
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
color: '#fff',
|
|
170
|
-
},
|
|
171
|
-
docSpec: {
|
|
172
|
-
fontSize: 12,
|
|
173
|
-
color: '#818CF8',
|
|
174
|
-
marginTop: 2,
|
|
175
|
-
},
|
|
176
|
-
docMetaRow: {
|
|
177
|
-
flexDirection: 'row',
|
|
178
|
-
marginTop: 4,
|
|
179
|
-
},
|
|
180
|
-
docMeta: {
|
|
181
|
-
fontSize: 11,
|
|
182
|
-
color: 'rgba(255,255,255,0.5)',
|
|
183
|
-
},
|
|
184
|
-
slotGrid: {
|
|
185
|
-
flexDirection: 'row',
|
|
186
|
-
flexWrap: 'wrap',
|
|
187
|
-
gap: 8,
|
|
188
|
-
},
|
|
438
|
+
docInfo: { flex: 1 },
|
|
439
|
+
docName: { fontSize: 15, fontWeight: 'bold', color: '#fff' },
|
|
440
|
+
docSpec: { fontSize: 12, color: '#818CF8', marginTop: 2 },
|
|
441
|
+
docMetaRow: { flexDirection: 'row', marginTop: 4 },
|
|
442
|
+
docMeta: { fontSize: 11, color: 'rgba(255,255,255,0.5)' },
|
|
443
|
+
slotGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
|
|
189
444
|
slotBtn: {
|
|
190
445
|
backgroundColor: 'rgba(99,102,241,0.2)',
|
|
191
446
|
paddingVertical: 8,
|
|
@@ -194,11 +449,7 @@ const styles = StyleSheet.create({
|
|
|
194
449
|
borderWidth: 1,
|
|
195
450
|
borderColor: 'rgba(99,102,241,0.4)',
|
|
196
451
|
},
|
|
197
|
-
slotText: {
|
|
198
|
-
color: '#E0E7FF',
|
|
199
|
-
fontSize: 13,
|
|
200
|
-
fontWeight: '600',
|
|
201
|
-
},
|
|
452
|
+
slotText: { color: '#E0E7FF', fontSize: 13, fontWeight: '600' },
|
|
202
453
|
successCard: {
|
|
203
454
|
backgroundColor: 'rgba(16,185,129,0.1)',
|
|
204
455
|
borderRadius: 16,
|
|
@@ -208,15 +459,8 @@ const styles = StyleSheet.create({
|
|
|
208
459
|
borderColor: 'rgba(16,185,129,0.3)',
|
|
209
460
|
alignItems: 'center',
|
|
210
461
|
},
|
|
211
|
-
successIcon: {
|
|
212
|
-
|
|
213
|
-
},
|
|
214
|
-
successTitle: {
|
|
215
|
-
fontSize: 18,
|
|
216
|
-
fontWeight: 'bold',
|
|
217
|
-
color: '#34D399',
|
|
218
|
-
marginBottom: 16,
|
|
219
|
-
},
|
|
462
|
+
successIcon: { marginBottom: 8 },
|
|
463
|
+
successTitle: { fontSize: 18, fontWeight: 'bold', color: '#34D399', marginBottom: 16 },
|
|
220
464
|
detailRow: {
|
|
221
465
|
flexDirection: 'row',
|
|
222
466
|
alignItems: 'center',
|
|
@@ -224,10 +468,7 @@ const styles = StyleSheet.create({
|
|
|
224
468
|
gap: 6,
|
|
225
469
|
width: '100%',
|
|
226
470
|
},
|
|
227
|
-
detailText: {
|
|
228
|
-
fontSize: 14,
|
|
229
|
-
color: 'rgba(255,255,255,0.8)',
|
|
230
|
-
},
|
|
471
|
+
detailText: { fontSize: 14, color: 'rgba(255,255,255,0.8)' },
|
|
231
472
|
apptItem: {
|
|
232
473
|
backgroundColor: 'rgba(0,0,0,0.2)',
|
|
233
474
|
padding: 12,
|
|
@@ -236,4 +477,123 @@ const styles = StyleSheet.create({
|
|
|
236
477
|
borderLeftWidth: 3,
|
|
237
478
|
borderLeftColor: '#818CF8',
|
|
238
479
|
},
|
|
480
|
+
|
|
481
|
+
// ── Provider Selection ────────────────────────────────────────────────────
|
|
482
|
+
tabRow: {
|
|
483
|
+
flexDirection: 'row',
|
|
484
|
+
marginBottom: 12,
|
|
485
|
+
gap: 8,
|
|
486
|
+
},
|
|
487
|
+
tab: {
|
|
488
|
+
flex: 1,
|
|
489
|
+
flexDirection: 'row',
|
|
490
|
+
alignItems: 'center',
|
|
491
|
+
justifyContent: 'center',
|
|
492
|
+
gap: 4,
|
|
493
|
+
paddingVertical: 8,
|
|
494
|
+
borderRadius: 10,
|
|
495
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
496
|
+
borderWidth: 1,
|
|
497
|
+
borderColor: 'rgba(255,255,255,0.08)',
|
|
498
|
+
},
|
|
499
|
+
tabActive: {
|
|
500
|
+
backgroundColor: 'rgba(99,102,241,0.2)',
|
|
501
|
+
borderColor: 'rgba(99,102,241,0.5)',
|
|
502
|
+
},
|
|
503
|
+
tabText: { fontSize: 12, color: 'rgba(255,255,255,0.5)', fontWeight: '600' },
|
|
504
|
+
tabTextActive: { color: '#818CF8' },
|
|
505
|
+
nearbyItem: {
|
|
506
|
+
flexDirection: 'row',
|
|
507
|
+
backgroundColor: 'rgba(0,0,0,0.2)',
|
|
508
|
+
padding: 12,
|
|
509
|
+
borderRadius: 12,
|
|
510
|
+
marginTop: 8,
|
|
511
|
+
alignItems: 'center',
|
|
512
|
+
gap: 10,
|
|
513
|
+
borderWidth: 1,
|
|
514
|
+
borderColor: 'rgba(255,255,255,0.05)',
|
|
515
|
+
},
|
|
516
|
+
nearbyIcon: {
|
|
517
|
+
width: 44,
|
|
518
|
+
height: 44,
|
|
519
|
+
borderRadius: 22,
|
|
520
|
+
backgroundColor: 'rgba(99,102,241,0.15)',
|
|
521
|
+
justifyContent: 'center',
|
|
522
|
+
alignItems: 'center',
|
|
523
|
+
},
|
|
524
|
+
bookBadge: {
|
|
525
|
+
backgroundColor: 'rgba(99,102,241,0.3)',
|
|
526
|
+
paddingHorizontal: 10,
|
|
527
|
+
paddingVertical: 5,
|
|
528
|
+
borderRadius: 8,
|
|
529
|
+
borderWidth: 1,
|
|
530
|
+
borderColor: 'rgba(99,102,241,0.5)',
|
|
531
|
+
},
|
|
532
|
+
bookBadgeText: { color: '#A5B4FC', fontSize: 12, fontWeight: '700' },
|
|
533
|
+
emptyText: { color: 'rgba(255,255,255,0.4)', fontSize: 13, textAlign: 'center', marginTop: 12 },
|
|
534
|
+
|
|
535
|
+
// ── Nearby Details ────────────────────────────────────────────────────────
|
|
536
|
+
nearbyDetailCard: {
|
|
537
|
+
borderColor: 'rgba(99,102,241,0.3)',
|
|
538
|
+
},
|
|
539
|
+
nearbyDetailHeader: {
|
|
540
|
+
flexDirection: 'row',
|
|
541
|
+
alignItems: 'center',
|
|
542
|
+
marginBottom: 12,
|
|
543
|
+
},
|
|
544
|
+
nearbyBtnRow: {
|
|
545
|
+
flexDirection: 'row',
|
|
546
|
+
gap: 10,
|
|
547
|
+
marginTop: 14,
|
|
548
|
+
},
|
|
549
|
+
nearbyBtn: {
|
|
550
|
+
flex: 1,
|
|
551
|
+
flexDirection: 'row',
|
|
552
|
+
alignItems: 'center',
|
|
553
|
+
justifyContent: 'center',
|
|
554
|
+
gap: 6,
|
|
555
|
+
backgroundColor: 'rgba(99,102,241,0.25)',
|
|
556
|
+
paddingVertical: 10,
|
|
557
|
+
borderRadius: 10,
|
|
558
|
+
borderWidth: 1,
|
|
559
|
+
borderColor: 'rgba(99,102,241,0.4)',
|
|
560
|
+
},
|
|
561
|
+
navBtn: {
|
|
562
|
+
backgroundColor: 'rgba(16,185,129,0.2)',
|
|
563
|
+
borderColor: 'rgba(16,185,129,0.4)',
|
|
564
|
+
},
|
|
565
|
+
nearbyBtnText: { color: '#fff', fontSize: 14, fontWeight: '600' },
|
|
566
|
+
nearbyNoBookNotice: {
|
|
567
|
+
flexDirection: 'row',
|
|
568
|
+
alignItems: 'center',
|
|
569
|
+
gap: 5,
|
|
570
|
+
marginTop: 12,
|
|
571
|
+
},
|
|
572
|
+
nearbyNoBookText: { color: 'rgba(255,255,255,0.35)', fontSize: 11 },
|
|
573
|
+
|
|
574
|
+
// ── Payment Card ──────────────────────────────────────────────────────────
|
|
575
|
+
paymentCard: {
|
|
576
|
+
borderColor: 'rgba(16,185,129,0.25)',
|
|
577
|
+
},
|
|
578
|
+
paymentSummary: {
|
|
579
|
+
backgroundColor: 'rgba(0,0,0,0.2)',
|
|
580
|
+
borderRadius: 10,
|
|
581
|
+
padding: 12,
|
|
582
|
+
marginBottom: 14,
|
|
583
|
+
gap: 6,
|
|
584
|
+
},
|
|
585
|
+
amountRow: { marginTop: 4 },
|
|
586
|
+
amountText: { fontSize: 20, fontWeight: 'bold', color: '#34D399', marginLeft: 6 },
|
|
587
|
+
paymentBtn: {
|
|
588
|
+
flexDirection: 'row',
|
|
589
|
+
alignItems: 'center',
|
|
590
|
+
gap: 12,
|
|
591
|
+
backgroundColor: 'rgba(255,255,255,0.06)',
|
|
592
|
+
padding: 14,
|
|
593
|
+
borderRadius: 12,
|
|
594
|
+
marginTop: 8,
|
|
595
|
+
borderWidth: 1,
|
|
596
|
+
borderColor: 'rgba(255,255,255,0.08)',
|
|
597
|
+
},
|
|
598
|
+
paymentBtnText: { flex: 1, color: '#fff', fontSize: 15, fontWeight: '600' },
|
|
239
599
|
});
|