medos-sdk 1.0.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.
@@ -0,0 +1,568 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { AppointmentService, } from "../services/AppointmentService";
4
+ import { PatientService } from "../services/PatientService";
5
+ export const AppointmentCalender = ({ onError, }) => {
6
+ const [step, setStep] = useState(0);
7
+ const [addresses, setAddresses] = useState([]);
8
+ const [addressDoctorsMap, setAddressDoctorsMap] = useState({});
9
+ const [selectedAddress, setSelectedAddress] = useState(null);
10
+ const [workspaceId, setWorkspaceId] = useState(null);
11
+ const [doctors, setDoctors] = useState([]);
12
+ const [selectedDoctor, setSelectedDoctor] = useState(null);
13
+ const [date, setDate] = useState("");
14
+ const [slots, setSlots] = useState([]);
15
+ const [selectedSlot, setSelectedSlot] = useState(null);
16
+ const [loading, setLoading] = useState(false);
17
+ const [error, setError] = useState(null);
18
+ const [patientName, setPatientName] = useState("");
19
+ const [patientAge, setPatientAge] = useState("");
20
+ const [patientAddress, setPatientAddress] = useState("");
21
+ const [patientEmail, setPatientEmail] = useState("");
22
+ const [patientGender, setPatientGender] = useState("");
23
+ const [problemFacing, setProblemFacing] = useState("");
24
+ const [consultationCharge, setConsultationCharge] = useState("");
25
+ const [countryCode, setCountryCode] = useState("+91");
26
+ const [patientPhone, setPatientPhone] = useState("");
27
+ const [otpCode, setOtpCode] = useState("");
28
+ const [otpSent, setOtpSent] = useState(false);
29
+ const [otpVerified, setOtpVerified] = useState(false);
30
+ const [otpSending, setOtpSending] = useState(false);
31
+ const [otpVerifying, setOtpVerifying] = useState(false);
32
+ useEffect(() => {
33
+ let mounted = true;
34
+ (async () => {
35
+ setLoading(true);
36
+ setError(null);
37
+ try {
38
+ let fetchedAddresses = [];
39
+ try {
40
+ const addrResp = await AppointmentService.getAddresses();
41
+ if (addrResp && Array.isArray(addrResp.addresses)) {
42
+ fetchedAddresses = addrResp.addresses;
43
+ if (addrResp.workspaceId && mounted) {
44
+ setWorkspaceId(addrResp.workspaceId);
45
+ }
46
+ }
47
+ }
48
+ catch (e) {
49
+ if (!mounted)
50
+ return;
51
+ throw e;
52
+ }
53
+ if (mounted && fetchedAddresses.length > 0) {
54
+ const addrMap = {};
55
+ const mappedAddrs = fetchedAddresses.map((a, idx) => {
56
+ const id = String(a.id ?? idx);
57
+ const label = a.completeAddress ?? a.label ?? a.address ?? `Address ${idx + 1}`;
58
+ const docs = Array.isArray(a.doctors)
59
+ ? a.doctors
60
+ : [];
61
+ addrMap[id] = docs || [];
62
+ return { id, label };
63
+ });
64
+ setAddresses(mappedAddrs);
65
+ setAddressDoctorsMap(addrMap);
66
+ const anyDoctorsExist = Object.values(addrMap).some((arr) => Array.isArray(arr) && arr.length > 0);
67
+ if (mappedAddrs.length === 1) {
68
+ const only = mappedAddrs[0];
69
+ setSelectedAddress(only.id);
70
+ const docsForAddr = addrMap[only.id] || [];
71
+ if (docsForAddr.length > 0) {
72
+ setDoctors(docsForAddr);
73
+ if (docsForAddr.length === 1) {
74
+ setSelectedDoctor(docsForAddr[0].id);
75
+ setStep(1);
76
+ }
77
+ else {
78
+ setStep(0);
79
+ }
80
+ }
81
+ else {
82
+ if (anyDoctorsExist) {
83
+ setError("No doctors at this address. Please choose a different address.");
84
+ setDoctors([]);
85
+ setStep(0);
86
+ }
87
+ else {
88
+ setError("No doctors available for the selected location(s).");
89
+ setDoctors([]);
90
+ setStep(0);
91
+ }
92
+ }
93
+ }
94
+ else {
95
+ setStep(0);
96
+ }
97
+ setLoading(false);
98
+ return;
99
+ }
100
+ if (mounted) {
101
+ setError("No addresses or doctors available.");
102
+ setAddresses([]);
103
+ setAddressDoctorsMap({});
104
+ setDoctors([]);
105
+ setStep(0);
106
+ }
107
+ }
108
+ catch (e) {
109
+ if (!mounted)
110
+ return;
111
+ const msg = e.message || "Failed to load addresses";
112
+ setError(msg);
113
+ onError?.(e);
114
+ }
115
+ finally {
116
+ if (mounted)
117
+ setLoading(false);
118
+ }
119
+ })();
120
+ return () => {
121
+ mounted = false;
122
+ };
123
+ }, []);
124
+ useEffect(() => {
125
+ let mounted = true;
126
+ (async () => {
127
+ if (!selectedAddress) {
128
+ setDoctors([]);
129
+ setSelectedDoctor(null);
130
+ return;
131
+ }
132
+ setLoading(true);
133
+ setError(null);
134
+ try {
135
+ const docsForAddr = addressDoctorsMap[selectedAddress] ?? [];
136
+ if (docsForAddr.length > 0) {
137
+ if (mounted) {
138
+ setDoctors(docsForAddr);
139
+ if (docsForAddr.length === 1) {
140
+ setSelectedDoctor(docsForAddr[0].id);
141
+ setStep(1);
142
+ }
143
+ else {
144
+ setSelectedDoctor(null);
145
+ }
146
+ }
147
+ }
148
+ else {
149
+ const otherHasDoctors = Object.entries(addressDoctorsMap).some(([key, docs]) => key !== selectedAddress && Array.isArray(docs) && docs.length > 0);
150
+ if (mounted) {
151
+ setDoctors([]);
152
+ setSelectedDoctor(null);
153
+ if (otherHasDoctors) {
154
+ setError("No doctors at this address. Please select a different address.");
155
+ }
156
+ else {
157
+ setError("No doctors available for the selected location(s).");
158
+ }
159
+ }
160
+ }
161
+ }
162
+ catch (e) {
163
+ if (!mounted)
164
+ return;
165
+ setError(e.message || "Failed to load doctors for address");
166
+ }
167
+ finally {
168
+ if (mounted)
169
+ setLoading(false);
170
+ }
171
+ })();
172
+ return () => {
173
+ mounted = false;
174
+ };
175
+ }, [selectedAddress, addressDoctorsMap]);
176
+ useEffect(() => {
177
+ if (!workspaceId || !selectedAddress || !selectedDoctor || !date) {
178
+ setSlots([]);
179
+ setSelectedSlot(null);
180
+ return;
181
+ }
182
+ let mounted = true;
183
+ setLoading(true);
184
+ setError(null);
185
+ (async () => {
186
+ try {
187
+ const s = await AppointmentService.fetchSlots(workspaceId, selectedAddress, selectedDoctor, date);
188
+ if (!mounted)
189
+ return;
190
+ setSlots(s || []);
191
+ }
192
+ catch (e) {
193
+ if (!mounted)
194
+ return;
195
+ setError(e.message || "Failed to load slots");
196
+ }
197
+ finally {
198
+ if (mounted)
199
+ setLoading(false);
200
+ }
201
+ })();
202
+ return () => {
203
+ mounted = false;
204
+ };
205
+ }, [workspaceId, selectedAddress, selectedDoctor, date]);
206
+ const goToNext = () => {
207
+ if (step === 0) {
208
+ if (addresses.length > 1 && !selectedAddress)
209
+ return;
210
+ if (doctors.length > 1 && !selectedDoctor)
211
+ return;
212
+ setStep(1);
213
+ return;
214
+ }
215
+ setStep((s) => Math.min(5, s + 1));
216
+ };
217
+ const goBack = () => {
218
+ setStep((s) => Math.max(0, s - 1));
219
+ };
220
+ const validatePhoneNumber = (phone) => {
221
+ const cleaned = phone.replace(/\D/g, "");
222
+ return cleaned.length >= 7 && cleaned.length <= 15;
223
+ };
224
+ const validateCountryCode = (code) => {
225
+ return /^\+[1-9]\d{0,3}$/.test(code);
226
+ };
227
+ const canProceedFromMergedStep = (() => {
228
+ if (addresses.length === 0 || doctors.length === 0)
229
+ return false;
230
+ if (addresses.length > 1 && !selectedAddress)
231
+ return false;
232
+ if (doctors.length > 1 && !selectedDoctor)
233
+ return false;
234
+ return true;
235
+ })();
236
+ const sendOtp = async () => {
237
+ setError(null);
238
+ if (!countryCode) {
239
+ setError("Please enter country code.");
240
+ return;
241
+ }
242
+ if (!validateCountryCode(countryCode)) {
243
+ setError("Please enter a valid country code (e.g., +91, +1).");
244
+ return;
245
+ }
246
+ if (!patientPhone) {
247
+ setError("Please enter phone number.");
248
+ return;
249
+ }
250
+ if (!validatePhoneNumber(patientPhone)) {
251
+ setError("Please enter a valid phone number (7-15 digits).");
252
+ return;
253
+ }
254
+ setOtpSending(true);
255
+ try {
256
+ await PatientService.sendPhoneVerificationOtp({
257
+ countryCode,
258
+ phoneNumber: patientPhone,
259
+ });
260
+ setOtpSent(true);
261
+ setError(null);
262
+ }
263
+ catch (e) {
264
+ const msg = e.message || "Failed to send OTP";
265
+ setError(msg);
266
+ onError?.(e);
267
+ }
268
+ finally {
269
+ setOtpSending(false);
270
+ }
271
+ };
272
+ const verifyOtp = async () => {
273
+ setError(null);
274
+ if (!countryCode || !patientPhone || !otpCode) {
275
+ setError("Please enter all required fields.");
276
+ return;
277
+ }
278
+ if (otpCode.length !== 6) {
279
+ setError("Please enter a 6-digit OTP code.");
280
+ return;
281
+ }
282
+ setOtpVerifying(true);
283
+ try {
284
+ await PatientService.verifyPhoneVerificationOtp({
285
+ countryCode,
286
+ phoneNumber: patientPhone,
287
+ otpCode,
288
+ });
289
+ setOtpVerified(true);
290
+ setError(null);
291
+ }
292
+ catch (e) {
293
+ const msg = e.message || "Invalid OTP code";
294
+ setError(msg);
295
+ onError?.(e);
296
+ }
297
+ finally {
298
+ setOtpVerifying(false);
299
+ }
300
+ };
301
+ const submitAppointment = async () => {
302
+ setError(null);
303
+ if (!selectedDoctor || !selectedSlot || !workspaceId || !selectedAddress) {
304
+ setError("Please ensure all selections are complete.");
305
+ return;
306
+ }
307
+ if (!otpVerified) {
308
+ setError("Please verify your phone number first.");
309
+ return;
310
+ }
311
+ setLoading(true);
312
+ try {
313
+ const nameParts = (patientName || "Patient").trim().split(/\s+/);
314
+ const firstName = nameParts[0] || "Patient";
315
+ const lastName = nameParts.slice(1).join(" ") || "";
316
+ const startDate = new Date(selectedSlot.start);
317
+ const endDate = new Date(selectedSlot.end);
318
+ const appointmentDate = startDate.toISOString().split("T")[0];
319
+ const formatTime = (date) => {
320
+ const hours = String(date.getHours()).padStart(2, "0");
321
+ const minutes = String(date.getMinutes()).padStart(2, "0");
322
+ return `${hours}:${minutes}`;
323
+ };
324
+ const fromDateTimeTs = formatTime(startDate);
325
+ const toDateTimeTs = formatTime(endDate);
326
+ const patientAddressPayload = patientAddress
327
+ ? {
328
+ addressLine1: patientAddress,
329
+ }
330
+ : undefined;
331
+ await AppointmentService.createAppointment({
332
+ workspaceId: workspaceId,
333
+ workspaceAddressId: selectedAddress,
334
+ doctorId: selectedDoctor,
335
+ mode: "OFFLINE",
336
+ appointmentDate,
337
+ fromDateTimeTs,
338
+ toDateTimeTs,
339
+ consultationCharge: consultationCharge || "0",
340
+ type: "CONSULTATION",
341
+ source: "SDK_POWERED_WEBSITE",
342
+ patientPayload: {
343
+ firstName,
344
+ lastName,
345
+ email: patientEmail || undefined,
346
+ countryCode,
347
+ phoneNumber: patientPhone,
348
+ age: patientAge ? parseInt(patientAge, 10) : undefined,
349
+ gender: patientGender
350
+ ? patientGender.toUpperCase()
351
+ : undefined,
352
+ },
353
+ patientAddress: patientAddressPayload,
354
+ });
355
+ setStep(5);
356
+ }
357
+ catch (e) {
358
+ const msg = e.message || "Failed to create appointment";
359
+ setError(msg);
360
+ onError?.(e);
361
+ }
362
+ finally {
363
+ setLoading(false);
364
+ }
365
+ };
366
+ const styles = {
367
+ container: {
368
+ display: "flex",
369
+ justifyContent: "center",
370
+ padding: 20,
371
+ fontFamily: "'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial",
372
+ background: "#f6f8fa",
373
+ },
374
+ card: {
375
+ width: "100%",
376
+ maxWidth: 720,
377
+ background: "#fff",
378
+ borderRadius: 12,
379
+ boxShadow: "0 8px 24px rgba(16,24,40,0.08)",
380
+ padding: 24,
381
+ boxSizing: "border-box",
382
+ },
383
+ header: {
384
+ display: "flex",
385
+ alignItems: "center",
386
+ justifyContent: "space-between",
387
+ marginBottom: 16,
388
+ },
389
+ title: { margin: 0, fontSize: 20, fontWeight: 600 },
390
+ stepper: {
391
+ display: "flex",
392
+ gap: 8,
393
+ alignItems: "center",
394
+ },
395
+ stepPill: (active = false) => ({
396
+ padding: "6px 10px",
397
+ borderRadius: 999,
398
+ background: active ? "#0b79f7" : "#eef2ff",
399
+ color: active ? "#fff" : "#333",
400
+ fontSize: 12,
401
+ fontWeight: 600,
402
+ }),
403
+ section: { marginTop: 12 },
404
+ label: {
405
+ display: "block",
406
+ fontSize: 13,
407
+ marginBottom: 6,
408
+ color: "#374151",
409
+ },
410
+ input: {
411
+ width: "100%",
412
+ padding: "10px 12px",
413
+ borderRadius: 8,
414
+ border: "1px solid #e6e9ef",
415
+ outline: "none",
416
+ fontSize: 14,
417
+ boxSizing: "border-box",
418
+ },
419
+ select: {
420
+ width: "100%",
421
+ padding: "10px 12px",
422
+ borderRadius: 8,
423
+ border: "1px solid #e6e9ef",
424
+ background: "#fff",
425
+ fontSize: 14,
426
+ },
427
+ actions: {
428
+ display: "flex",
429
+ gap: 8,
430
+ marginTop: 16,
431
+ justifyContent: "flex-end",
432
+ },
433
+ primaryBtn: {
434
+ background: "#0b79f7",
435
+ color: "#fff",
436
+ border: "none",
437
+ padding: "10px 14px",
438
+ borderRadius: 8,
439
+ cursor: "pointer",
440
+ fontWeight: 600,
441
+ },
442
+ secondaryBtn: {
443
+ background: "#fff",
444
+ color: "#0b254a",
445
+ border: "1px solid #e6e9ef",
446
+ padding: "10px 14px",
447
+ borderRadius: 8,
448
+ cursor: "pointer",
449
+ },
450
+ slotGrid: {
451
+ display: "grid",
452
+ gridTemplateColumns: "repeat(auto-fit,minmax(140px,1fr))",
453
+ gap: 12,
454
+ marginTop: 12,
455
+ },
456
+ slotCard: (selected = false) => ({
457
+ padding: 12,
458
+ borderRadius: 10,
459
+ border: selected ? "2px solid #0b79f7" : "1px solid #e6e9ef",
460
+ background: selected
461
+ ? "linear-gradient(180deg,#f0f6ff,#e9f2ff)"
462
+ : "#fff",
463
+ cursor: "pointer",
464
+ textAlign: "center",
465
+ }),
466
+ smallMuted: { fontSize: 12, color: "#6b7280" },
467
+ successCard: {
468
+ padding: 20,
469
+ borderRadius: 12,
470
+ background: "linear-gradient(90deg,#ecfdf5,#eff6ff)",
471
+ textAlign: "center",
472
+ },
473
+ };
474
+ return (_jsx("div", { style: styles.container, children: _jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.header, children: [_jsx("h2", { style: styles.title, children: "Book Appointment" }), _jsxs("div", { style: styles.stepper, children: [_jsx("div", { style: styles.stepPill(step === 0), children: "1 Address" }), _jsx("div", { style: styles.stepPill(step === 1), children: "2 Date" }), _jsx("div", { style: styles.stepPill(step === 2), children: "3 Slot" }), _jsx("div", { style: styles.stepPill(step === 3), children: "4 Phone" }), _jsx("div", { style: styles.stepPill(step === 4), children: "5 Details" })] })] }), loading && _jsx("div", { style: { marginBottom: 12 }, children: "Loading..." }), error && (_jsx("div", { style: {
475
+ marginBottom: 12,
476
+ color: "#ef4444",
477
+ fontWeight: 600,
478
+ }, children: error })), step === 0 && (_jsxs("div", { style: styles.section, children: [_jsxs("div", { style: {
479
+ display: "grid",
480
+ gridTemplateColumns: "1fr 1fr",
481
+ gap: 16,
482
+ }, children: [_jsxs("div", { children: [_jsx("label", { style: styles.label, children: "Address" }), addresses.length === 0 ? (_jsx("div", { style: styles.smallMuted, children: "No addresses available" })) : addresses.length === 1 ? (_jsx("div", { style: { ...styles.smallMuted, fontWeight: 600 }, children: `${addresses[0].label}` })) : (_jsxs("select", { style: styles.select, value: selectedAddress || "", onChange: (e) => {
483
+ setSelectedAddress(e.target.value || null);
484
+ setError(null);
485
+ }, children: [_jsx("option", { value: "", children: "-- choose address --" }), addresses.map((a) => (_jsx("option", { value: a.id, children: a.label }, a.id)))] }))] }), _jsxs("div", { children: [_jsx("label", { style: styles.label, children: "Doctor" }), doctors.length === 0 ? (_jsx("div", { style: styles.smallMuted, children: "No doctors available" })) : doctors.length === 1 ? (_jsx("div", { style: { ...styles.smallMuted, fontWeight: 600 }, children: `${doctors[0].name} ${doctors[0].specialty ? `• ${doctors[0].specialty}` : ""}` })) : (_jsxs("select", { style: styles.select, value: selectedDoctor || "", onChange: (e) => setSelectedDoctor(e.target.value), children: [_jsx("option", { value: "", children: "-- choose doctor --" }), doctors.map((d) => (_jsxs("option", { value: d.id, children: [d.name, " ", d.specialty ? `(${d.specialty})` : ""] }, d.id)))] }))] })] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: () => {
486
+ }, children: "Cancel" }), _jsx("button", { style: {
487
+ ...styles.primaryBtn,
488
+ opacity: canProceedFromMergedStep ? 1 : 0.6,
489
+ }, disabled: !canProceedFromMergedStep, onClick: goToNext, children: "Next" })] })] })), step === 1 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Select Date" }), _jsx("input", { style: styles.input, type: "date", value: date, onChange: (e) => setDate(e.target.value) }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: { ...styles.primaryBtn, opacity: date ? 1 : 0.6 }, disabled: !date, onClick: () => setStep(2), children: "Next" })] })] })), step === 2 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Choose Time Slot" }), slots.length === 0 ? (_jsx("div", { style: styles.smallMuted, children: "No slots available for selected date" })) : (_jsx("div", { style: styles.slotGrid, children: slots.map((s) => {
490
+ const start = new Date(s.start).toLocaleTimeString([], {
491
+ hour: "2-digit",
492
+ minute: "2-digit",
493
+ });
494
+ const end = new Date(s.end).toLocaleTimeString([], {
495
+ hour: "2-digit",
496
+ minute: "2-digit",
497
+ });
498
+ const selected = selectedSlot?.start === s.start &&
499
+ selectedSlot?.end === s.end;
500
+ return (_jsxs("div", { style: styles.slotCard(selected), onClick: () => setSelectedSlot(s), children: [_jsx("div", { style: { fontWeight: 700 }, children: `${start} — ${end}` }), _jsx("div", { style: styles.smallMuted })] }, s.id ?? `${s.start}-${s.end}`));
501
+ }) })), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: {
502
+ ...styles.primaryBtn,
503
+ opacity: selectedSlot ? 1 : 0.6,
504
+ }, disabled: !selectedSlot, onClick: () => setStep(3), children: "Next" })] })] })), step === 3 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Country Code" }), _jsx("input", { style: styles.input, placeholder: "+91", value: countryCode, onChange: (e) => {
505
+ let value = e.target.value;
506
+ if (value && !value.startsWith("+")) {
507
+ value = "+" + value;
508
+ }
509
+ value = value.replace(/[^\d+]/g, "");
510
+ setCountryCode(value);
511
+ } }), countryCode && !validateCountryCode(countryCode) && (_jsx("div", { style: { marginTop: 4, fontSize: 12, color: "#ef4444" }, children: "Please enter a valid country code (e.g., +91, +1)" })), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Phone Number" }), _jsx("input", { style: styles.input, type: "tel", placeholder: "9311840587", value: patientPhone, onChange: (e) => {
512
+ const value = e.target.value.replace(/\D/g, "");
513
+ setPatientPhone(value);
514
+ }, disabled: otpSent, maxLength: 15 }), patientPhone && !validatePhoneNumber(patientPhone) && (_jsx("div", { style: { marginTop: 4, fontSize: 12, color: "#ef4444" }, children: "Phone number should be 7-15 digits" })), !otpSent ? (_jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: {
515
+ ...styles.primaryBtn,
516
+ opacity: countryCode && patientPhone ? 1 : 0.6,
517
+ }, disabled: !countryCode ||
518
+ !patientPhone ||
519
+ otpSending ||
520
+ !validateCountryCode(countryCode) ||
521
+ !validatePhoneNumber(patientPhone), onClick: sendOtp, children: otpSending ? "Sending..." : "Send OTP" })] })) : (_jsx(_Fragment, { children: otpVerified ? (_jsxs(_Fragment, { children: [_jsx("div", { style: {
522
+ marginTop: 12,
523
+ padding: 12,
524
+ borderRadius: 8,
525
+ background: "#ecfdf5",
526
+ color: "#10b981",
527
+ fontWeight: 600,
528
+ }, children: "\u2713 Phone verified successfully" }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: styles.primaryBtn, onClick: goToNext, children: "Continue to Details" })] })] })) : (_jsxs(_Fragment, { children: [_jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Enter OTP" }), _jsx("input", { style: styles.input, type: "text", placeholder: "Enter 6-digit OTP", value: otpCode, onChange: (e) => setOtpCode(e.target.value), maxLength: 6 }), _jsxs("div", { style: {
529
+ marginTop: 8,
530
+ fontSize: 12,
531
+ color: "#6b7280",
532
+ }, children: ["OTP sent to ", countryCode, " ", patientPhone] }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: () => {
533
+ setOtpSent(false);
534
+ setOtpCode("");
535
+ setOtpVerified(false);
536
+ }, children: "Change Number" }), _jsx("button", { style: {
537
+ ...styles.primaryBtn,
538
+ opacity: otpCode.length === 6 ? 1 : 0.6,
539
+ }, disabled: otpCode.length !== 6 || otpVerifying, onClick: verifyOtp, children: otpVerifying ? "Verifying..." : "Verify OTP" })] })] })) }))] })), step === 4 && (_jsxs("div", { style: styles.section, children: [_jsx("label", { style: styles.label, children: "Patient Name" }), _jsx("input", { style: styles.input, placeholder: "Full name", value: patientName, onChange: (e) => setPatientName(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Age" }), _jsx("input", { style: styles.input, type: "number", placeholder: "Age", value: patientAge, onChange: (e) => setPatientAge(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Email (Optional)" }), _jsx("input", { style: styles.input, type: "email", placeholder: "patient@example.com", value: patientEmail, onChange: (e) => setPatientEmail(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Gender (Optional)" }), _jsxs("select", { style: styles.select, value: patientGender, onChange: (e) => setPatientGender(e.target.value), children: [_jsx("option", { value: "", children: "-- Select Gender --" }), _jsx("option", { value: "MALE", children: "Male" }), _jsx("option", { value: "FEMALE", children: "Female" }), _jsx("option", { value: "OTHER", children: "Other" })] }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Address" }), _jsx("textarea", { style: { ...styles.input, minHeight: 80, resize: "vertical" }, placeholder: "Enter your address", value: patientAddress, onChange: (e) => setPatientAddress(e.target.value) }), _jsx("label", { style: { ...styles.label, marginTop: 12 }, children: "Problem Facing" }), _jsx("textarea", { style: { ...styles.input, minHeight: 80, resize: "vertical" }, placeholder: "Describe the problem you're facing", value: problemFacing, onChange: (e) => setProblemFacing(e.target.value) }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { style: styles.secondaryBtn, onClick: goBack, children: "Back" }), _jsx("button", { style: {
540
+ ...styles.primaryBtn,
541
+ opacity: patientName && otpVerified ? 1 : 0.6,
542
+ }, disabled: !patientName || !otpVerified || loading, onClick: submitAppointment, children: loading ? "Booking..." : "Book Appointment" })] })] })), step === 5 && (_jsxs("div", { style: { ...styles.section, textAlign: "center" }, children: [_jsxs("div", { style: styles.successCard, children: [_jsx("div", { style: {
543
+ fontSize: 32,
544
+ color: "#10b981",
545
+ marginBottom: 8,
546
+ }, children: "\u2713" }), _jsx("div", { style: { fontWeight: 700, marginBottom: 6 }, children: "Appointment Confirmed" }), _jsxs("div", { style: styles.smallMuted, children: ["Thank you, ", patientName || "Patient", ". Your appointment is confirmed."] })] }), _jsx("div", { style: {
547
+ marginTop: 14,
548
+ display: "flex",
549
+ justifyContent: "center",
550
+ }, children: _jsx("button", { style: { ...styles.primaryBtn, width: 160 }, onClick: () => {
551
+ setStep(0);
552
+ setSelectedSlot(null);
553
+ setSelectedDoctor(null);
554
+ setDate("");
555
+ setPatientName("");
556
+ setPatientAge("");
557
+ setPatientAddress("");
558
+ setPatientEmail("");
559
+ setPatientGender("");
560
+ setProblemFacing("");
561
+ setConsultationCharge("");
562
+ setCountryCode("+91");
563
+ setPatientPhone("");
564
+ setOtpCode("");
565
+ setOtpSent(false);
566
+ setOtpVerified(false);
567
+ }, children: "Book Another" }) })] }))] }) }));
568
+ };
@@ -0,0 +1,7 @@
1
+ export { MedosClient } from "./client/MedosClient";
2
+ export { AppointmentCalender } from "./components/AppointmentCalender";
3
+ export * from "./appointments/provider";
4
+ export * from "./appointments/types";
5
+ export * from "./appointment-calendar/provider";
6
+ export * from "./appointment-calendar/types";
7
+ export { PatientService, SendPhoneVerificationOtpPayload, VerifyPhoneVerificationOtpPayload, } from "./services/PatientService";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { MedosClient } from "./client/MedosClient";
2
+ export { AppointmentCalender } from "./components/AppointmentCalender";
3
+ export * from "./appointments/provider";
4
+ export * from "./appointments/types";
5
+ export * from "./appointment-calendar/provider";
6
+ export * from "./appointment-calendar/types";
7
+ export { PatientService, } from "./services/PatientService";
@@ -0,0 +1,86 @@
1
+ export type Doctor = {
2
+ id: string;
3
+ name: string;
4
+ specialty?: string;
5
+ [key: string]: any;
6
+ };
7
+ export type Slot = {
8
+ start: string;
9
+ end: string;
10
+ id?: string;
11
+ [key: string]: any;
12
+ };
13
+ type PatientPayload = {
14
+ firstName: string;
15
+ lastName: string;
16
+ email?: string;
17
+ countryCode: string;
18
+ phoneNumber: string;
19
+ age?: number;
20
+ gender?: "MALE" | "FEMALE" | "OTHER";
21
+ };
22
+ type PatientAddressPayload = {
23
+ addressLine1?: string;
24
+ city?: string;
25
+ state?: string;
26
+ country?: string;
27
+ zipcode?: string;
28
+ landmark?: string;
29
+ };
30
+ type BookAppointmentPayload = {
31
+ workspaceId?: string | number;
32
+ workspaceAddressId: string | number;
33
+ doctorId: string | number;
34
+ mode?: "OFFLINE" | "ONLINE";
35
+ appointmentDate: string;
36
+ fromDateTimeTs: string;
37
+ toDateTimeTs: string;
38
+ consultationCharge?: string;
39
+ type?: "CONSULTATION" | string;
40
+ source?: string;
41
+ patientPayload: PatientPayload;
42
+ patientAddress?: PatientAddressPayload;
43
+ attachments?: File[];
44
+ };
45
+ type AppointmentPayload = {
46
+ workspaceId?: string | number;
47
+ workspaceAddressId: string | number;
48
+ doctorId: string | number;
49
+ mode?: "OFFLINE" | "ONLINE";
50
+ appointmentDate: string;
51
+ fromDateTimeTs: string;
52
+ toDateTimeTs: string;
53
+ consultationCharge?: string;
54
+ type?: string;
55
+ source?: string;
56
+ patientPayload: PatientPayload;
57
+ patientAddress?: PatientAddressPayload;
58
+ attachments?: File[];
59
+ };
60
+ type AddressItem = {
61
+ id: number | string;
62
+ completeAddress?: string;
63
+ label?: string;
64
+ address?: string;
65
+ countryCode?: string | null;
66
+ phoneNumber?: string | null;
67
+ doctorsCount?: number;
68
+ totalConfiguredAppointments?: number;
69
+ doctors?: Doctor[];
70
+ [key: string]: any;
71
+ };
72
+ type AddressesResponse = {
73
+ totalAddresses?: number;
74
+ totalDoctors?: number;
75
+ totalConfiguredAppointments?: number;
76
+ totalPartialConfiguredAppointments?: number;
77
+ totalNotConfiguredAppointments?: number;
78
+ workspaceId?: number | string;
79
+ addresses: AddressItem[];
80
+ };
81
+ declare const AppointmentService: {
82
+ getAddresses(): Promise<AddressesResponse>;
83
+ fetchSlots(workspaceId: string | number, addressId: string | number, doctorId: string | number, appointmentDate: string): Promise<Slot[]>;
84
+ createAppointment(payload: BookAppointmentPayload): Promise<any>;
85
+ };
86
+ export { AppointmentService, AppointmentPayload, BookAppointmentPayload, PatientPayload, PatientAddressPayload, AddressesResponse, AddressItem, };