medos-sdk 1.1.7 → 1.1.8

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 (27) hide show
  1. package/dist/components/SuccessStep.js +1 -1
  2. package/dist/vanilla/AppointmentCalendarWidget.d.ts +8 -0
  3. package/dist/vanilla/AppointmentCalendarWidget.js +463 -158
  4. package/dist/vanilla/EnquiryFormWidget.d.ts +2 -0
  5. package/dist/vanilla/EnquiryFormWidget.js +155 -100
  6. package/dist/vanilla/components/VanillaCalendar.d.ts +32 -0
  7. package/dist/vanilla/components/VanillaCalendar.js +366 -0
  8. package/dist/vanilla/components/VanillaIcons.d.ts +17 -0
  9. package/dist/vanilla/components/VanillaIcons.js +268 -0
  10. package/dist/vanilla/components/VanillaSelect.d.ts +46 -0
  11. package/dist/vanilla/components/VanillaSelect.js +523 -0
  12. package/dist/vanilla/components/index.d.ts +3 -0
  13. package/dist/vanilla/components/index.js +3 -0
  14. package/dist/vanilla/components/theme-injector.d.ts +1 -0
  15. package/dist/vanilla/components/theme-injector.js +447 -0
  16. package/dist/vanilla/enquiry-widget.js +1366 -100
  17. package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +8 -0
  18. package/dist/vanilla/vanilla/EnquiryFormWidget.d.ts +2 -0
  19. package/dist/vanilla/vanilla/components/VanillaCalendar.d.ts +32 -0
  20. package/dist/vanilla/vanilla/components/VanillaIcons.d.ts +17 -0
  21. package/dist/vanilla/vanilla/components/VanillaSelect.d.ts +46 -0
  22. package/dist/vanilla/vanilla/components/index.d.ts +3 -0
  23. package/dist/vanilla/vanilla/components/theme-injector.d.ts +1 -0
  24. package/dist/vanilla/vanilla/widget.d.ts +2 -0
  25. package/dist/vanilla/widget.d.ts +2 -0
  26. package/dist/vanilla/widget.js +2213 -257
  27. package/package.json +1 -1
@@ -1,13 +1,24 @@
1
1
  import { AppointmentService, } from "../services/AppointmentService";
2
2
  import { PatientService } from "../services/PatientService";
3
3
  import { MedosClient } from "../client/MedosClient";
4
- import { INITIAL_STATE, } from "../components/types";
4
+ import { INITIAL_STATE, COUNTRY_CODES, GENDER_OPTIONS, BLOOD_GROUP_OPTIONS, } from "../components/types";
5
5
  import { validatePhoneNumber, validateCountryCode, } from "../components/validation";
6
6
  import { formatDateToISO, parsePatientName } from "../components/utils";
7
+ import { VanillaIcons } from "./components/VanillaIcons";
8
+ import { VanillaSelect } from "./components/VanillaSelect";
9
+ import { VanillaCalendar } from "./components/VanillaCalendar";
10
+ import { injectThemedStyles } from "./components/theme-injector";
7
11
  class AppointmentCalendarWidget {
8
12
  constructor(container, options) {
9
13
  this.mounted = true;
10
14
  this.doctors = [];
15
+ this.addressSelect = null;
16
+ this.doctorSelect = null;
17
+ this.calendar = null;
18
+ this.countryCodeSelect = null;
19
+ this.genderSelect = null;
20
+ this.bloodGroupSelect = null;
21
+ injectThemedStyles();
11
22
  if (typeof container === "string") {
12
23
  const el = document.getElementById(container);
13
24
  if (!el) {
@@ -195,8 +206,27 @@ class AppointmentCalendarWidget {
195
206
  return false;
196
207
  return true;
197
208
  }
209
+ updateSubmitButtonState() {
210
+ const submitBtn = this.container.querySelector("#medos-btn-submit");
211
+ if (submitBtn) {
212
+ const canSubmit = this.state.patientName &&
213
+ this.state.patientAddress &&
214
+ this.state.patientCity &&
215
+ this.state.patientState &&
216
+ this.state.patientCountry &&
217
+ this.state.patientZipcode &&
218
+ this.state.patientEmail &&
219
+ this.state.patientAge &&
220
+ this.state.otpVerified;
221
+ submitBtn.disabled = !(canSubmit && !this.state.loading);
222
+ }
223
+ }
198
224
  async sendOtp() {
199
225
  this.setState({ error: null });
226
+ console.log("sendOtp called - Current state:", {
227
+ countryCode: this.state.countryCode,
228
+ patientPhone: this.state.patientPhone,
229
+ });
200
230
  if (!this.state.countryCode) {
201
231
  this.setState({ error: "Please enter country code." });
202
232
  return;
@@ -220,6 +250,10 @@ class AppointmentCalendarWidget {
220
250
  this.setState({ otpSending: true });
221
251
  this.render();
222
252
  try {
253
+ console.log("Sending OTP to:", {
254
+ countryCode: this.state.countryCode,
255
+ phoneNumber: this.state.patientPhone,
256
+ });
223
257
  await PatientService.sendPhoneVerificationOtp({
224
258
  countryCode: this.state.countryCode,
225
259
  phoneNumber: this.state.patientPhone,
@@ -227,7 +261,10 @@ class AppointmentCalendarWidget {
227
261
  this.setState({ otpSent: true, error: null });
228
262
  }
229
263
  catch (e) {
230
- const msg = e.message || "Failed to send OTP";
264
+ console.error("Send OTP error:", e);
265
+ const msg = e?.response?.data?.message ||
266
+ e?.message ||
267
+ "Failed to send OTP. Please try again.";
231
268
  this.setState({ error: msg });
232
269
  this.options.onError?.(e);
233
270
  }
@@ -251,6 +288,11 @@ class AppointmentCalendarWidget {
251
288
  this.setState({ otpVerifying: true });
252
289
  this.render();
253
290
  try {
291
+ console.log("Verifying OTP with:", {
292
+ countryCode: this.state.countryCode,
293
+ phoneNumber: this.state.patientPhone,
294
+ otpCode: this.state.otpCode,
295
+ });
254
296
  await PatientService.verifyPhoneVerificationOtp({
255
297
  countryCode: this.state.countryCode,
256
298
  phoneNumber: this.state.patientPhone,
@@ -259,7 +301,10 @@ class AppointmentCalendarWidget {
259
301
  this.setState({ otpVerified: true, error: null });
260
302
  }
261
303
  catch (e) {
262
- const msg = e.message || "Invalid OTP code";
304
+ console.error("OTP verification error:", e);
305
+ const msg = e?.response?.data?.message ||
306
+ e?.message ||
307
+ "Invalid OTP code. Please try again.";
263
308
  this.setState({ error: msg });
264
309
  this.options.onError?.(e);
265
310
  }
@@ -420,6 +465,112 @@ class AppointmentCalendarWidget {
420
465
  </div>
421
466
  `;
422
467
  this.attachEventListeners();
468
+ this.initializeCustomComponents();
469
+ }
470
+ initializeCustomComponents() {
471
+ if (this.state.step === 0) {
472
+ const addressContainer = this.container.querySelector("#medos-address-select-container");
473
+ if (addressContainer && this.state.addresses.length > 0) {
474
+ const addressOptions = this.state.addresses.map((a) => ({
475
+ value: a.id.toString(),
476
+ label: a.label || `Address ${a.id}`,
477
+ }));
478
+ this.addressSelect = new VanillaSelect(addressContainer, addressOptions, {
479
+ placeholder: "Select a location",
480
+ onValueChange: (value) => {
481
+ this.handleAddressChange(Number(value) || null);
482
+ },
483
+ });
484
+ if (this.state.selectedAddress) {
485
+ this.addressSelect.setValue(this.state.selectedAddress.toString());
486
+ }
487
+ }
488
+ const doctorContainer = this.container.querySelector("#medos-doctor-select-container");
489
+ if (doctorContainer && this.doctors.length > 0) {
490
+ const doctorOptions = this.doctors.map((d) => ({
491
+ value: d.id.toString(),
492
+ label: d.name + (d.specialty ? ` • ${d.specialty}` : ""),
493
+ }));
494
+ this.doctorSelect = new VanillaSelect(doctorContainer, doctorOptions, {
495
+ placeholder: "Select a doctor",
496
+ onValueChange: (value) => {
497
+ this.state.selectedDoctor = Number(value) || null;
498
+ const selectedDoc = this.doctors.find((d) => d.id === this.state.selectedDoctor);
499
+ if (selectedDoc && selectedDoc.consultationCharge) {
500
+ this.state.consultationCharge = selectedDoc.consultationCharge;
501
+ }
502
+ this.render();
503
+ },
504
+ });
505
+ if (this.state.selectedDoctor) {
506
+ this.doctorSelect.setValue(this.state.selectedDoctor.toString());
507
+ }
508
+ }
509
+ }
510
+ if (this.state.step === 1) {
511
+ const calendarContainer = this.container.querySelector("#medos-calendar-container");
512
+ if (calendarContainer) {
513
+ this.calendar = new VanillaCalendar(calendarContainer, {
514
+ selectedDate: this.state.selectedDate || undefined,
515
+ pastDisabled: true,
516
+ onSelect: (date) => {
517
+ this.state.selectedDate = date;
518
+ this.render();
519
+ },
520
+ });
521
+ }
522
+ }
523
+ if (this.state.step === 3) {
524
+ const countryCodeContainer = this.container.querySelector("#medos-country-code-container");
525
+ if (countryCodeContainer) {
526
+ const countryOptions = COUNTRY_CODES.map((c) => ({
527
+ value: c.code,
528
+ label: c.label,
529
+ }));
530
+ this.countryCodeSelect = new VanillaSelect(countryCodeContainer, countryOptions, {
531
+ placeholder: "Country",
532
+ onValueChange: (value) => {
533
+ this.state.countryCode = value;
534
+ const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
535
+ if (sendOtpBtn) {
536
+ const canSendOtp = this.state.countryCode &&
537
+ this.state.patientPhone.length >= 10;
538
+ sendOtpBtn.disabled = !canSendOtp;
539
+ }
540
+ },
541
+ });
542
+ if (this.state.countryCode) {
543
+ this.countryCodeSelect.setValue(this.state.countryCode);
544
+ }
545
+ }
546
+ }
547
+ if (this.state.step === 4) {
548
+ const genderContainer = this.container.querySelector("#medos-gender-container");
549
+ if (genderContainer) {
550
+ this.genderSelect = new VanillaSelect(genderContainer, GENDER_OPTIONS, {
551
+ placeholder: "Select gender",
552
+ onValueChange: (value) => {
553
+ this.state.patientGender = value;
554
+ this.updateSubmitButtonState();
555
+ },
556
+ });
557
+ if (this.state.patientGender) {
558
+ this.genderSelect.setValue(this.state.patientGender);
559
+ }
560
+ }
561
+ const bloodGroupContainer = this.container.querySelector("#medos-blood-group-container");
562
+ if (bloodGroupContainer) {
563
+ this.bloodGroupSelect = new VanillaSelect(bloodGroupContainer, BLOOD_GROUP_OPTIONS, {
564
+ placeholder: "Select blood group (optional)",
565
+ onValueChange: (value) => {
566
+ this.state.bloodGroup = value;
567
+ },
568
+ });
569
+ if (this.state.bloodGroup) {
570
+ this.bloodGroupSelect.setValue(this.state.bloodGroup);
571
+ }
572
+ }
573
+ }
423
574
  }
424
575
  renderStep() {
425
576
  switch (this.state.step) {
@@ -442,72 +593,122 @@ class AppointmentCalendarWidget {
442
593
  renderStep0() {
443
594
  const canProceed = this.canProceedFromMergedStep();
444
595
  return `
445
- <div class="medos-appointment-section">
446
- <div class="medos-appointment-form-grid-2col">
447
- <div>
448
- <label class="medos-appointment-label">Address</label>
449
- ${this.state.addresses.length === 0
450
- ? '<div class="medos-appointment-small-muted">No addresses available</div>'
451
- : this.state.addresses.length === 1
452
- ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.state.addresses[0].label || "")}</div>`
453
- : `
454
- <select class="medos-appointment-select" id="medos-address-select">
455
- <option value="">-- choose address --</option>
456
- ${this.state.addresses
457
- .map((a) => `<option value="${this.escapeHtml(a.id.toString())}" ${this.state.selectedAddress === a.id ? "selected" : ""}>${this.escapeHtml(a.label || "")}</option>`)
458
- .join("")}
459
- </select>
460
- `}
596
+ <div class="medos-section-card">
597
+ <div class="medos-section-header">
598
+ ${VanillaIcons.mapPin(14)}
599
+ <span class="medos-section-title">Location & Doctor</span>
600
+ </div>
601
+ <div class="medos-section-body">
602
+ <div class="medos-form-group">
603
+ <label class="medos-label">Preferred Location <span class="medos-required">*</span></label>
604
+ <div id="medos-address-select-container"></div>
461
605
  </div>
462
- <div>
463
- <label class="medos-appointment-label">Doctor</label>
464
- ${this.doctors.length === 0
465
- ? '<div class="medos-appointment-small-muted">No doctors available</div>'
466
- : this.doctors.length === 1
467
- ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty
468
- ? ` • ${this.escapeHtml(this.doctors[0].specialty)}`
469
- : ""}</div>`
470
- : `
471
- <select class="medos-appointment-select" id="medos-doctor-select">
472
- <option value="">-- choose doctor --</option>
473
- ${this.doctors
474
- .map((d) => `<option value="${this.escapeHtml(d.id.toString())}" ${this.state.selectedDoctor === d.id ? "selected" : ""}>${this.escapeHtml(d.name)}${d.specialty
475
- ? ` (${this.escapeHtml(d.specialty)})`
476
- : ""}</option>`)
477
- .join("")}
478
- </select>
479
- `}
606
+ <div class="medos-form-group">
607
+ <label class="medos-label">Preferred Doctor <span class="medos-required">*</span></label>
608
+ <div id="medos-doctor-select-container"></div>
609
+ </div>
610
+ <div class="medos-form-group">
611
+ <label class="medos-label">Chief Complaint <span class="medos-optional">(optional)</span></label>
612
+ <textarea
613
+ class="medos-textarea"
614
+ id="medos-chief-complaint"
615
+ placeholder="Enter Chief Complaint or Appointment Notes"
616
+ ></textarea>
480
617
  </div>
481
- </div>
482
- <div class="medos-appointment-actions">
483
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-cancel">Cancel</button>
484
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!canProceed ? "disabled" : ""} style="opacity: ${canProceed ? 1 : 0.6}">Next</button>
485
618
  </div>
486
619
  </div>
620
+ <div class="medos-actions">
621
+ <button class="medos-btn medos-btn-primary" id="medos-btn-next" ${!canProceed ? "disabled" : ""}>Next</button>
622
+ </div>
487
623
  `;
488
624
  }
489
625
  renderStep1() {
490
626
  const dateStr = formatDateToISO(this.state.selectedDate);
627
+ const selectedDoctor = this.doctors.find((d) => d.id === this.state.selectedDoctor);
628
+ const selectedDoctorData = this.doctors.find((d) => d.id === this.state.selectedDoctor);
629
+ const consultationCharge = selectedDoctorData?.consultationCharge ||
630
+ selectedDoctor?.consultationCharge ||
631
+ this.state.consultationCharge ||
632
+ "N/A";
491
633
  return `
492
- <div class="medos-appointment-section">
493
- <label class="medos-appointment-label">Select Date</label>
494
- <input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(dateStr)}" />
495
- <div class="medos-appointment-actions">
496
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
497
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!dateStr ? "disabled" : ""} style="opacity: ${dateStr ? 1 : 0.6}">Next</button>
634
+ <div class="medos-section-card">
635
+ <div class="medos-section-header">
636
+ ${VanillaIcons.consultationType(14)}
637
+ <span class="medos-section-title">Consultation Type</span>
638
+ </div>
639
+ <div class="medos-section-body">
640
+ <div class="medos-consultation-options">
641
+ <label class="medos-radio-option ${this.state.consultationMode === "OFFLINE" ? "selected" : ""}">
642
+ <input
643
+ type="radio"
644
+ name="consultationMode"
645
+ value="OFFLINE"
646
+ ${this.state.consultationMode === "OFFLINE" ? "checked" : ""}
647
+ class="medos-radio-input"
648
+ />
649
+ <span class="medos-radio-label">In-Person Visit</span>
650
+ </label>
651
+ <label class="medos-radio-option ${this.state.consultationMode === "ONLINE" ? "selected" : ""}">
652
+ <input
653
+ type="radio"
654
+ name="consultationMode"
655
+ value="ONLINE"
656
+ ${this.state.consultationMode === "ONLINE" ? "checked" : ""}
657
+ class="medos-radio-input"
658
+ />
659
+ <span class="medos-radio-label">Online Consultation</span>
660
+ </label>
661
+ </div>
662
+ <div class="medos-consultation-charge">
663
+ <span class="medos-charge-label">Consultation Charge:</span>
664
+ <span class="medos-charge-value">${consultationCharge !== "N/A"
665
+ ? "₹" + consultationCharge
666
+ : consultationCharge}</span>
667
+ </div>
668
+ </div>
669
+ </div>
670
+
671
+ <div class="medos-section-card">
672
+ <div class="medos-section-header">
673
+ ${VanillaIcons.dateTime(14)}
674
+ <span class="medos-section-title">Select Date</span>
498
675
  </div>
676
+ <div class="medos-section-body">
677
+ <div id="medos-calendar-container"></div>
678
+ </div>
679
+ </div>
680
+
681
+ <div class="medos-actions">
682
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
683
+ <button class="medos-btn medos-btn-primary" id="medos-btn-next" ${dateStr ? "" : "disabled"}>Next</button>
499
684
  </div>
500
685
  `;
501
686
  }
502
687
  renderStep2() {
688
+ const dateDisplay = this.state.selectedDate
689
+ ? this.state.selectedDate.toLocaleDateString("en-US", {
690
+ weekday: "long",
691
+ year: "numeric",
692
+ month: "long",
693
+ day: "numeric",
694
+ })
695
+ : "";
503
696
  return `
504
- <div class="medos-appointment-section">
505
- <label class="medos-appointment-label">Choose Time Slot</label>
506
- ${this.state.slots.length === 0
507
- ? '<div class="medos-appointment-small-muted">No slots available for selected date</div>'
697
+ <div class="medos-section-card">
698
+ <div class="medos-section-header">
699
+ ${VanillaIcons.clock(14)}
700
+ <span class="medos-section-title">Select Time Slot</span>
701
+ </div>
702
+ <div class="medos-section-body">
703
+ <div class="medos-selected-date-display">
704
+ ${VanillaIcons.dateTime(16)}
705
+ <span>${dateDisplay}</span>
706
+ </div>
707
+ ${this.state.slots.length === 0
708
+ ? '<div class="medos-empty-slots">No slots available for selected date</div>'
508
709
  : `
509
- <div class="medos-appointment-slot-grid">
510
- ${this.state.slots
710
+ <div class="medos-slots-grid">
711
+ ${this.state.slots
511
712
  .map((s) => {
512
713
  const start = new Date(s.start).toLocaleTimeString([], {
513
714
  hour: "2-digit",
@@ -520,19 +721,28 @@ class AppointmentCalendarWidget {
520
721
  const selected = this.state.selectedSlot?.start === s.start &&
521
722
  this.state.selectedSlot?.end === s.end;
522
723
  return `
523
- <div class="medos-appointment-slot-card ${selected ? "selected" : ""}" data-slot-id="${this.escapeHtml(s.id || `${s.start}-${s.end}`)}" data-slot-start="${this.escapeHtml(s.start)}" data-slot-end="${this.escapeHtml(s.end)}">
524
- <div class="medos-appointment-slot-time">${start} ${end}</div>
525
- </div>
526
- `;
724
+ <div class="medos-slot-card ${selected ? "selected" : ""}"
725
+ data-slot-id="${this.escapeHtml(s.id || `${s.start}-${s.end}`)}"
726
+ data-slot-start="${this.escapeHtml(s.start)}"
727
+ data-slot-end="${this.escapeHtml(s.end)}">
728
+ <span class="medos-slot-time">${start}</span>
729
+ <span class="medos-slot-separator">-</span>
730
+ <span class="medos-slot-time">${end}</span>
731
+ ${selected
732
+ ? `<span class="medos-slot-check">${VanillaIcons.check(14)}</span>`
733
+ : ""}
734
+ </div>
735
+ `;
527
736
  })
528
737
  .join("")}
529
- </div>
530
- `}
531
- <div class="medos-appointment-actions">
532
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
533
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!this.state.selectedSlot ? "disabled" : ""} style="opacity: ${this.state.selectedSlot ? 1 : 0.6}">Next</button>
738
+ </div>
739
+ `}
534
740
  </div>
535
741
  </div>
742
+ <div class="medos-actions">
743
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
744
+ <button class="medos-btn medos-btn-primary" id="medos-btn-next" ${this.state.selectedSlot ? "" : "disabled"} style="opacity: ${this.state.selectedSlot ? 1 : 0.6}">Next</button>
745
+ </div>
536
746
  `;
537
747
  }
538
748
  renderStep3() {
@@ -541,46 +751,86 @@ class AppointmentCalendarWidget {
541
751
  const canSendOtp = countryCodeValid && phoneValid && !this.state.otpSending;
542
752
  if (!this.state.otpSent) {
543
753
  return `
544
- <div class="medos-appointment-section">
545
- <label class="medos-appointment-label">Country Code</label>
546
- <input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.state.countryCode)}" />
547
- ${this.state.countryCode && !countryCodeValid
548
- ? '<div class="medos-appointment-validation-error">Please enter a valid country code (e.g., +91, +1)</div>'
549
- : ""}
550
- <label class="medos-appointment-label" style="margin-top: 12px">Phone Number</label>
551
- <input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.state.patientPhone)}" maxlength="15" />
552
- ${this.state.patientPhone && !phoneValid
553
- ? '<div class="medos-appointment-validation-error">Phone number should be 7-15 digits</div>'
754
+ <div class="medos-section-card">
755
+ <div class="medos-section-header">
756
+ ${VanillaIcons.phone(14)}
757
+ <span class="medos-section-title">Phone Verification</span>
758
+ </div>
759
+ <div class="medos-section-body">
760
+ <p class="medos-section-description">Please enter your phone number for verification</p>
761
+ <div class="medos-phone-input-row">
762
+ <div class="medos-country-code-wrapper">
763
+ <div id="medos-country-code-container"></div>
764
+ </div>
765
+ <div class="medos-phone-wrapper">
766
+ <input
767
+ type="tel"
768
+ class="medos-input"
769
+ id="medos-phone"
770
+ placeholder="Enter phone number"
771
+ value="${this.escapeHtml(this.state.patientPhone)}"
772
+ maxlength="15"
773
+ />
774
+ </div>
775
+ </div>
776
+ ${this.state.patientPhone && !phoneValid
777
+ ? '<div class="medos-validation-error">Phone number should be 7-15 digits</div>'
554
778
  : ""}
555
- <div class="medos-appointment-actions">
556
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
557
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-send-otp" ${!canSendOtp ? "disabled" : ""} style="opacity: ${canSendOtp ? 1 : 0.6}">${this.state.otpSending ? "Sending..." : "Send OTP"}</button>
558
779
  </div>
559
780
  </div>
781
+ <div class="medos-actions">
782
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
783
+ <button class="medos-btn medos-btn-primary" id="medos-btn-send-otp" ${canSendOtp ? "" : "disabled"}>${this.state.otpSending ? "Sending..." : "Send OTP"}</button>
784
+ </div>
560
785
  `;
561
786
  }
562
787
  if (this.state.otpVerified) {
563
788
  return `
564
- <div class="medos-appointment-section">
565
- <div class="medos-appointment-verified">✓ Phone verified successfully</div>
566
- <div class="medos-appointment-actions">
567
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
568
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next">Continue to Details</button>
789
+ <div class="medos-section-card">
790
+ <div class="medos-section-header">
791
+ ${VanillaIcons.phone(14)}
792
+ <span class="medos-section-title">Phone Verification</span>
793
+ </div>
794
+ <div class="medos-section-body">
795
+ <div class="medos-verified-badge">
796
+ ${VanillaIcons.successBadge(20)}
797
+ <span>Phone verified successfully</span>
798
+ </div>
799
+ <div class="medos-verified-number">${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
569
800
  </div>
570
801
  </div>
802
+ <div class="medos-actions">
803
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
804
+ <button class="medos-btn medos-btn-primary" id="medos-btn-next">Continue</button>
805
+ </div>
571
806
  `;
572
807
  }
573
808
  return `
574
- <div class="medos-appointment-section">
575
- <label class="medos-appointment-label">Enter OTP</label>
576
- <input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.state.otpCode)}" maxlength="6" />
577
- <div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
578
- <div class="medos-appointment-actions">
579
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-change-number">Change Number</button>
580
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.state.otpCode.length !== 6 || this.state.otpVerifying
581
- ? "disabled"
582
- : ""} style="opacity: ${this.state.otpCode.length === 6 && !this.state.otpVerifying ? 1 : 0.6}">${this.state.otpVerifying ? "Verifying..." : "Verify OTP"}</button>
809
+ <div class="medos-section-card">
810
+ <div class="medos-section-header">
811
+ ${VanillaIcons.phone(14)}
812
+ <span class="medos-section-title">Enter OTP</span>
583
813
  </div>
814
+ <div class="medos-section-body">
815
+ <p class="medos-section-description">Enter the 6-digit code sent to ${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</p>
816
+ <div class="medos-otp-input-wrapper">
817
+ <input
818
+ type="text"
819
+ class="medos-input medos-otp-input"
820
+ id="medos-otp"
821
+ placeholder="Enter 6-digit OTP"
822
+ value="${this.escapeHtml(this.state.otpCode)}"
823
+ maxlength="6"
824
+ />
825
+ </div>
826
+ <button class="medos-link-btn" id="medos-btn-change-number">Change phone number</button>
827
+ </div>
828
+ </div>
829
+ <div class="medos-actions">
830
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-resend-otp">Resend OTP</button>
831
+ <button class="medos-btn medos-btn-primary" id="medos-btn-verify-otp" ${this.state.otpCode.length === 6 && !this.state.otpVerifying
832
+ ? ""
833
+ : "disabled"}>${this.state.otpVerifying ? "Verifying..." : "Verify OTP"}</button>
584
834
  </div>
585
835
  `;
586
836
  }
@@ -593,50 +843,79 @@ class AppointmentCalendarWidget {
593
843
  this.state.patientZipcode &&
594
844
  this.state.otpVerified;
595
845
  return `
596
- <div class="medos-appointment-section">
597
- <label class="medos-appointment-label">Patient Name</label>
598
- <input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.state.patientName)}" />
599
- <label class="medos-appointment-label" style="margin-top: 12px">Age</label>
600
- <input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.state.patientAge)}" />
601
- <label class="medos-appointment-label" style="margin-top: 12px">Email (Optional)</label>
602
- <input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.state.patientEmail)}" />
603
- <label class="medos-appointment-label" style="margin-top: 12px">Gender (Optional)</label>
604
- <select class="medos-appointment-select" id="medos-patient-gender">
605
- <option value="">-- Select Gender --</option>
606
- <option value="MALE" ${this.state.patientGender === "MALE" ? "selected" : ""}>Male</option>
607
- <option value="FEMALE" ${this.state.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
608
- <option value="OTHER" ${this.state.patientGender === "OTHER" ? "selected" : ""}>Other</option>
609
- </select>
610
- <label class="medos-appointment-label" style="margin-top: 12px">Address Line 1 *</label>
611
- <input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.state.patientAddress)}" />
612
- <div class="medos-appointment-form-grid">
613
- <div>
614
- <label class="medos-appointment-label">City *</label>
615
- <input type="text" class="medos-appointment-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.state.patientCity)}" />
846
+ <div class="medos-section-card">
847
+ <div class="medos-section-header">
848
+ ${VanillaIcons.user(14)}
849
+ <span class="medos-section-title">Patient Information</span>
850
+ </div>
851
+ <div class="medos-section-body">
852
+ <div class="medos-form-row">
853
+ <div class="medos-form-group medos-form-group-flex">
854
+ <label class="medos-label">Full Name <span class="medos-required">*</span></label>
855
+ <input type="text" class="medos-input" id="medos-patient-name" placeholder="Enter full name" value="${this.escapeHtml(this.state.patientName)}" />
856
+ </div>
857
+ <div class="medos-form-group medos-form-group-small">
858
+ <label class="medos-label">Age</label>
859
+ <input type="number" class="medos-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.state.patientAge)}" />
860
+ </div>
616
861
  </div>
617
- <div>
618
- <label class="medos-appointment-label">State *</label>
619
- <input type="text" class="medos-appointment-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.state.patientState)}" />
862
+ <div class="medos-form-row">
863
+ <div class="medos-form-group medos-form-group-flex">
864
+ <label class="medos-label">Email</label>
865
+ <input type="email" class="medos-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.state.patientEmail)}" />
866
+ </div>
867
+ <div class="medos-form-group medos-form-group-small">
868
+ <label class="medos-label">Gender</label>
869
+ <div id="medos-gender-container"></div>
870
+ </div>
620
871
  </div>
872
+ <div class="medos-form-group">
873
+ <label class="medos-label">Blood Group <span class="medos-optional">(optional)</span></label>
874
+ <div id="medos-blood-group-container"></div>
875
+ </div>
876
+ </div>
877
+ </div>
878
+
879
+ <div class="medos-section-card">
880
+ <div class="medos-section-header">
881
+ ${VanillaIcons.mapPin(14)}
882
+ <span class="medos-section-title">Address Details</span>
621
883
  </div>
622
- <div class="medos-appointment-form-grid">
623
- <div>
624
- <label class="medos-appointment-label">Country *</label>
625
- <input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.state.patientCountry)}" />
884
+ <div class="medos-section-body">
885
+ <div class="medos-form-group">
886
+ <label class="medos-label">Address Line 1 <span class="medos-required">*</span></label>
887
+ <input type="text" class="medos-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.state.patientAddress)}" />
626
888
  </div>
627
- <div>
628
- <label class="medos-appointment-label">Zipcode *</label>
629
- <input type="text" class="medos-appointment-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.state.patientZipcode)}" />
889
+ <div class="medos-form-row">
890
+ <div class="medos-form-group">
891
+ <label class="medos-label">City <span class="medos-required">*</span></label>
892
+ <input type="text" class="medos-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.state.patientCity)}" />
893
+ </div>
894
+ <div class="medos-form-group">
895
+ <label class="medos-label">State <span class="medos-required">*</span></label>
896
+ <input type="text" class="medos-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.state.patientState)}" />
897
+ </div>
898
+ </div>
899
+ <div class="medos-form-row">
900
+ <div class="medos-form-group">
901
+ <label class="medos-label">Country <span class="medos-required">*</span></label>
902
+ <input type="text" class="medos-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.state.patientCountry)}" />
903
+ </div>
904
+ <div class="medos-form-group">
905
+ <label class="medos-label">Zipcode <span class="medos-required">*</span></label>
906
+ <input type="text" class="medos-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.state.patientZipcode)}" />
907
+ </div>
908
+ </div>
909
+ <div class="medos-form-group">
910
+ <label class="medos-label">Landmark <span class="medos-optional">(optional)</span></label>
911
+ <input type="text" class="medos-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.state.patientLandmark)}" />
630
912
  </div>
631
913
  </div>
632
- <label class="medos-appointment-label" style="margin-top: 12px">Landmark (Optional)</label>
633
- <input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.state.patientLandmark)}" />
634
- <label class="medos-appointment-label" style="margin-top: 12px">Problem Facing</label>
635
- <textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.state.patientName)}</textarea>
636
- <div class="medos-appointment-actions">
637
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
638
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-submit" ${!canSubmit || this.state.loading ? "disabled" : ""} style="opacity: ${canSubmit && !this.state.loading ? 1 : 0.6}">${this.state.loading ? "Booking..." : "Book Appointment"}</button>
639
- </div>
914
+ </div>
915
+
916
+ <div class="medos-actions">
917
+ <button class="medos-btn medos-btn-secondary" id="medos-btn-back">Back</button>
918
+ <button class="medos-btn medos-btn-primary" id="medos-btn-submit" ${canSubmit && !this.state.loading ? "" : "disabled"}>${this.state.loading ? "Booking..." : "Book Appointment"}</button>
640
919
  </div>
641
920
  `;
642
921
  }
@@ -755,35 +1034,20 @@ class AppointmentCalendarWidget {
755
1034
  `;
756
1035
  }
757
1036
  attachEventListeners() {
758
- const addressSelect = this.container.querySelector("#medos-address-select");
759
- if (addressSelect) {
760
- addressSelect.addEventListener("change", (e) => {
761
- const target = e.target;
762
- this.handleAddressChange(Number(target.value) || null);
763
- });
764
- }
765
- const doctorSelect = this.container.querySelector("#medos-doctor-select");
766
- if (doctorSelect) {
767
- doctorSelect.addEventListener("change", (e) => {
768
- const target = e.target;
769
- this.state.selectedDoctor = Number(target.value) || null;
770
- this.render();
771
- });
772
- }
773
- const dateInput = this.container.querySelector("#medos-date-input");
774
- if (dateInput) {
775
- dateInput.addEventListener("change", (e) => {
1037
+ const consultationRadios = this.container.querySelectorAll('input[name="consultationMode"]');
1038
+ consultationRadios.forEach((radio) => {
1039
+ radio.addEventListener("change", (e) => {
776
1040
  const target = e.target;
777
- this.state.selectedDate = new Date(target.value);
1041
+ this.state.consultationMode = target.value;
778
1042
  this.render();
779
1043
  });
780
- }
781
- const slotCards = this.container.querySelectorAll(".medos-appointment-slot-card");
1044
+ });
1045
+ const slotCards = this.container.querySelectorAll(".medos-slot-card");
782
1046
  slotCards.forEach((card) => {
783
1047
  card.addEventListener("click", () => {
784
- const slotId = card.getAttribute("data-slot-id");
785
- const slotStart = card.getAttribute("data-slot-start");
786
- const slotEnd = card.getAttribute("data-slot-end");
1048
+ const slotId = card.dataset.slotId;
1049
+ const slotStart = card.dataset.slotStart;
1050
+ const slotEnd = card.dataset.slotEnd;
787
1051
  if (slotStart && slotEnd) {
788
1052
  this.state.selectedSlot = {
789
1053
  start: slotStart,
@@ -805,7 +1069,11 @@ class AppointmentCalendarWidget {
805
1069
  value = value.replace(/[^\d+]/g, "");
806
1070
  this.state.countryCode = value;
807
1071
  target.value = value;
808
- this.render();
1072
+ const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
1073
+ if (sendOtpBtn) {
1074
+ const canSendOtp = this.state.countryCode && this.state.patientPhone.length >= 10;
1075
+ sendOtpBtn.disabled = !canSendOtp;
1076
+ }
809
1077
  });
810
1078
  }
811
1079
  const phoneInput = this.container.querySelector("#medos-phone");
@@ -814,7 +1082,11 @@ class AppointmentCalendarWidget {
814
1082
  const target = e.target;
815
1083
  this.state.patientPhone = target.value.replace(/\D/g, "");
816
1084
  target.value = this.state.patientPhone;
817
- this.render();
1085
+ const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
1086
+ if (sendOtpBtn) {
1087
+ const canSendOtp = this.state.countryCode && this.state.patientPhone.length >= 10;
1088
+ sendOtpBtn.disabled = !canSendOtp;
1089
+ }
818
1090
  });
819
1091
  }
820
1092
  const otpInput = this.container.querySelector("#medos-otp");
@@ -822,7 +1094,10 @@ class AppointmentCalendarWidget {
822
1094
  otpInput.addEventListener("input", (e) => {
823
1095
  const target = e.target;
824
1096
  this.state.otpCode = target.value;
825
- this.render();
1097
+ const verifyBtn = this.container.querySelector("#medos-btn-verify-otp");
1098
+ if (verifyBtn) {
1099
+ verifyBtn.disabled = !(this.state.otpCode.length === 6 && !this.state.otpVerifying);
1100
+ }
826
1101
  });
827
1102
  }
828
1103
  const patientNameInput = this.container.querySelector("#medos-patient-name");
@@ -830,6 +1105,7 @@ class AppointmentCalendarWidget {
830
1105
  patientNameInput.addEventListener("input", (e) => {
831
1106
  const target = e.target;
832
1107
  this.state.patientName = target.value;
1108
+ this.updateSubmitButtonState();
833
1109
  });
834
1110
  }
835
1111
  const patientAgeInput = this.container.querySelector("#medos-patient-age");
@@ -837,6 +1113,7 @@ class AppointmentCalendarWidget {
837
1113
  patientAgeInput.addEventListener("input", (e) => {
838
1114
  const target = e.target;
839
1115
  this.state.patientAge = target.value;
1116
+ this.updateSubmitButtonState();
840
1117
  });
841
1118
  }
842
1119
  const patientEmailInput = this.container.querySelector("#medos-patient-email");
@@ -844,6 +1121,7 @@ class AppointmentCalendarWidget {
844
1121
  patientEmailInput.addEventListener("input", (e) => {
845
1122
  const target = e.target;
846
1123
  this.state.patientEmail = target.value;
1124
+ this.updateSubmitButtonState();
847
1125
  });
848
1126
  }
849
1127
  const patientGenderSelect = this.container.querySelector("#medos-patient-gender");
@@ -858,6 +1136,7 @@ class AppointmentCalendarWidget {
858
1136
  patientAddressInput.addEventListener("input", (e) => {
859
1137
  const target = e.target;
860
1138
  this.state.patientAddress = target.value;
1139
+ this.updateSubmitButtonState();
861
1140
  });
862
1141
  }
863
1142
  const patientCityInput = this.container.querySelector("#medos-patient-city");
@@ -865,6 +1144,7 @@ class AppointmentCalendarWidget {
865
1144
  patientCityInput.addEventListener("input", (e) => {
866
1145
  const target = e.target;
867
1146
  this.state.patientCity = target.value;
1147
+ this.updateSubmitButtonState();
868
1148
  });
869
1149
  }
870
1150
  const patientStateInput = this.container.querySelector("#medos-patient-state");
@@ -872,6 +1152,7 @@ class AppointmentCalendarWidget {
872
1152
  patientStateInput.addEventListener("input", (e) => {
873
1153
  const target = e.target;
874
1154
  this.state.patientState = target.value;
1155
+ this.updateSubmitButtonState();
875
1156
  });
876
1157
  }
877
1158
  const patientCountryInput = this.container.querySelector("#medos-patient-country");
@@ -879,6 +1160,7 @@ class AppointmentCalendarWidget {
879
1160
  patientCountryInput.addEventListener("input", (e) => {
880
1161
  const target = e.target;
881
1162
  this.state.patientCountry = target.value;
1163
+ this.updateSubmitButtonState();
882
1164
  });
883
1165
  }
884
1166
  const patientZipcodeInput = this.container.querySelector("#medos-patient-zipcode");
@@ -886,6 +1168,7 @@ class AppointmentCalendarWidget {
886
1168
  patientZipcodeInput.addEventListener("input", (e) => {
887
1169
  const target = e.target;
888
1170
  this.state.patientZipcode = target.value;
1171
+ this.updateSubmitButtonState();
889
1172
  });
890
1173
  }
891
1174
  const patientLandmarkInput = this.container.querySelector("#medos-patient-landmark");
@@ -897,7 +1180,11 @@ class AppointmentCalendarWidget {
897
1180
  }
898
1181
  const nextBtn = this.container.querySelector("#medos-btn-next");
899
1182
  if (nextBtn) {
900
- nextBtn.addEventListener("click", () => this.goToNext());
1183
+ nextBtn.addEventListener("click", () => {
1184
+ if (!nextBtn.disabled) {
1185
+ this.goToNext();
1186
+ }
1187
+ });
901
1188
  }
902
1189
  const backBtn = this.container.querySelector("#medos-btn-back");
903
1190
  if (backBtn) {
@@ -905,11 +1192,19 @@ class AppointmentCalendarWidget {
905
1192
  }
906
1193
  const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
907
1194
  if (sendOtpBtn) {
908
- sendOtpBtn.addEventListener("click", () => this.sendOtp());
1195
+ sendOtpBtn.addEventListener("click", () => {
1196
+ if (!sendOtpBtn.disabled) {
1197
+ this.sendOtp();
1198
+ }
1199
+ });
909
1200
  }
910
1201
  const verifyOtpBtn = this.container.querySelector("#medos-btn-verify-otp");
911
1202
  if (verifyOtpBtn) {
912
- verifyOtpBtn.addEventListener("click", () => this.verifyOtp());
1203
+ verifyOtpBtn.addEventListener("click", () => {
1204
+ if (!verifyOtpBtn.disabled) {
1205
+ this.verifyOtp();
1206
+ }
1207
+ });
913
1208
  }
914
1209
  const changeNumberBtn = this.container.querySelector("#medos-btn-change-number");
915
1210
  if (changeNumberBtn) {
@@ -918,9 +1213,19 @@ class AppointmentCalendarWidget {
918
1213
  this.render();
919
1214
  });
920
1215
  }
1216
+ const resendOtpBtn = this.container.querySelector("#medos-btn-resend-otp");
1217
+ if (resendOtpBtn) {
1218
+ resendOtpBtn.addEventListener("click", () => {
1219
+ this.sendOtp();
1220
+ });
1221
+ }
921
1222
  const submitBtn = this.container.querySelector("#medos-btn-submit");
922
1223
  if (submitBtn) {
923
- submitBtn.addEventListener("click", () => this.submitAppointment());
1224
+ submitBtn.addEventListener("click", () => {
1225
+ if (!submitBtn.disabled) {
1226
+ this.submitAppointment();
1227
+ }
1228
+ });
924
1229
  }
925
1230
  const bookAnotherBtn = this.container.querySelector("#medos-btn-book-another");
926
1231
  if (bookAnotherBtn) {