medos-sdk 1.1.6 → 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 (40) hide show
  1. package/dist/components/AppointmentCalender.js +13 -2
  2. package/dist/components/AppointmentConfirmationStep.d.ts +24 -0
  3. package/dist/components/AppointmentConfirmationStep.js +110 -0
  4. package/dist/components/Icons/SuccessIcon.d.ts +8 -0
  5. package/dist/components/Icons/SuccessIcon.js +14 -0
  6. package/dist/components/SuccessStep.js +1 -1
  7. package/dist/services/EnquiryService.d.ts +1 -1
  8. package/dist/services/EnquiryService.js +5 -8
  9. package/dist/services/WorkspaceService.d.ts +9 -0
  10. package/dist/services/WorkspaceService.js +17 -0
  11. package/dist/vanilla/AppointmentCalendarWidget.d.ts +12 -0
  12. package/dist/vanilla/AppointmentCalendarWidget.js +573 -167
  13. package/dist/vanilla/EnquiryFormWidget.d.ts +3 -0
  14. package/dist/vanilla/EnquiryFormWidget.js +229 -120
  15. package/dist/vanilla/components/AppointmentConfirmationStep.d.ts +24 -0
  16. package/dist/vanilla/components/Icons/SuccessIcon.d.ts +8 -0
  17. package/dist/vanilla/components/VanillaCalendar.d.ts +32 -0
  18. package/dist/vanilla/components/VanillaCalendar.js +366 -0
  19. package/dist/vanilla/components/VanillaIcons.d.ts +17 -0
  20. package/dist/vanilla/components/VanillaIcons.js +268 -0
  21. package/dist/vanilla/components/VanillaSelect.d.ts +46 -0
  22. package/dist/vanilla/components/VanillaSelect.js +523 -0
  23. package/dist/vanilla/components/index.d.ts +3 -0
  24. package/dist/vanilla/components/index.js +3 -0
  25. package/dist/vanilla/components/theme-injector.d.ts +1 -0
  26. package/dist/vanilla/components/theme-injector.js +447 -0
  27. package/dist/vanilla/enquiry-widget.js +1445 -128
  28. package/dist/vanilla/services/EnquiryService.d.ts +1 -1
  29. package/dist/vanilla/services/WorkspaceService.d.ts +9 -0
  30. package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +12 -0
  31. package/dist/vanilla/vanilla/EnquiryFormWidget.d.ts +3 -0
  32. package/dist/vanilla/vanilla/components/VanillaCalendar.d.ts +32 -0
  33. package/dist/vanilla/vanilla/components/VanillaIcons.d.ts +17 -0
  34. package/dist/vanilla/vanilla/components/VanillaSelect.d.ts +46 -0
  35. package/dist/vanilla/vanilla/components/index.d.ts +3 -0
  36. package/dist/vanilla/vanilla/components/theme-injector.d.ts +1 -0
  37. package/dist/vanilla/vanilla/widget.d.ts +2 -0
  38. package/dist/vanilla/widget.d.ts +2 -0
  39. package/dist/vanilla/widget.js +2402 -294
  40. 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
  }
@@ -382,9 +427,10 @@ class AppointmentCalendarWidget {
382
427
  this.state.step = Math.max(0, this.state.step - 1);
383
428
  this.render();
384
429
  }
385
- reset() {
430
+ async reset() {
386
431
  this.state = { ...INITIAL_STATE };
387
432
  this.doctors = [];
433
+ await this.loadAddresses();
388
434
  this.render();
389
435
  }
390
436
  setState(updates) {
@@ -419,6 +465,112 @@ class AppointmentCalendarWidget {
419
465
  </div>
420
466
  `;
421
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
+ }
422
574
  }
423
575
  renderStep() {
424
576
  switch (this.state.step) {
@@ -441,72 +593,122 @@ class AppointmentCalendarWidget {
441
593
  renderStep0() {
442
594
  const canProceed = this.canProceedFromMergedStep();
443
595
  return `
444
- <div class="medos-appointment-section">
445
- <div class="medos-appointment-form-grid-2col">
446
- <div>
447
- <label class="medos-appointment-label">Address</label>
448
- ${this.state.addresses.length === 0
449
- ? '<div class="medos-appointment-small-muted">No addresses available</div>'
450
- : this.state.addresses.length === 1
451
- ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.state.addresses[0].label || "")}</div>`
452
- : `
453
- <select class="medos-appointment-select" id="medos-address-select">
454
- <option value="">-- choose address --</option>
455
- ${this.state.addresses
456
- .map((a) => `<option value="${this.escapeHtml(a.id.toString())}" ${this.state.selectedAddress === a.id ? "selected" : ""}>${this.escapeHtml(a.label || "")}</option>`)
457
- .join("")}
458
- </select>
459
- `}
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>
460
605
  </div>
461
- <div>
462
- <label class="medos-appointment-label">Doctor</label>
463
- ${this.doctors.length === 0
464
- ? '<div class="medos-appointment-small-muted">No doctors available</div>'
465
- : this.doctors.length === 1
466
- ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty
467
- ? ` • ${this.escapeHtml(this.doctors[0].specialty)}`
468
- : ""}</div>`
469
- : `
470
- <select class="medos-appointment-select" id="medos-doctor-select">
471
- <option value="">-- choose doctor --</option>
472
- ${this.doctors
473
- .map((d) => `<option value="${this.escapeHtml(d.id.toString())}" ${this.state.selectedDoctor === d.id ? "selected" : ""}>${this.escapeHtml(d.name)}${d.specialty
474
- ? ` (${this.escapeHtml(d.specialty)})`
475
- : ""}</option>`)
476
- .join("")}
477
- </select>
478
- `}
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>
479
617
  </div>
480
- </div>
481
- <div class="medos-appointment-actions">
482
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-cancel">Cancel</button>
483
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!canProceed ? "disabled" : ""} style="opacity: ${canProceed ? 1 : 0.6}">Next</button>
484
618
  </div>
485
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>
486
623
  `;
487
624
  }
488
625
  renderStep1() {
489
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";
490
633
  return `
491
- <div class="medos-appointment-section">
492
- <label class="medos-appointment-label">Select Date</label>
493
- <input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(dateStr)}" />
494
- <div class="medos-appointment-actions">
495
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
496
- <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>
675
+ </div>
676
+ <div class="medos-section-body">
677
+ <div id="medos-calendar-container"></div>
497
678
  </div>
498
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>
684
+ </div>
499
685
  `;
500
686
  }
501
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
+ : "";
502
696
  return `
503
- <div class="medos-appointment-section">
504
- <label class="medos-appointment-label">Choose Time Slot</label>
505
- ${this.state.slots.length === 0
506
- ? '<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>'
507
709
  : `
508
- <div class="medos-appointment-slot-grid">
509
- ${this.state.slots
710
+ <div class="medos-slots-grid">
711
+ ${this.state.slots
510
712
  .map((s) => {
511
713
  const start = new Date(s.start).toLocaleTimeString([], {
512
714
  hour: "2-digit",
@@ -519,19 +721,28 @@ class AppointmentCalendarWidget {
519
721
  const selected = this.state.selectedSlot?.start === s.start &&
520
722
  this.state.selectedSlot?.end === s.end;
521
723
  return `
522
- <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)}">
523
- <div class="medos-appointment-slot-time">${start} ${end}</div>
524
- </div>
525
- `;
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
+ `;
526
736
  })
527
737
  .join("")}
528
- </div>
529
- `}
530
- <div class="medos-appointment-actions">
531
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
532
- <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
+ `}
533
740
  </div>
534
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>
535
746
  `;
536
747
  }
537
748
  renderStep3() {
@@ -540,47 +751,87 @@ class AppointmentCalendarWidget {
540
751
  const canSendOtp = countryCodeValid && phoneValid && !this.state.otpSending;
541
752
  if (!this.state.otpSent) {
542
753
  return `
543
- <div class="medos-appointment-section">
544
- <label class="medos-appointment-label">Country Code</label>
545
- <input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.state.countryCode)}" />
546
- ${this.state.countryCode && !countryCodeValid
547
- ? '<div class="medos-appointment-validation-error">Please enter a valid country code (e.g., +91, +1)</div>'
548
- : ""}
549
- <label class="medos-appointment-label" style="margin-top: 12px">Phone Number</label>
550
- <input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.state.patientPhone)}" maxlength="15" />
551
- ${this.state.patientPhone && !phoneValid
552
- ? '<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>'
553
778
  : ""}
554
- <div class="medos-appointment-actions">
555
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
556
- <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>
557
779
  </div>
558
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>
559
785
  `;
560
786
  }
561
787
  if (this.state.otpVerified) {
562
788
  return `
563
- <div class="medos-appointment-section">
564
- <div class="medos-appointment-verified">✓ Phone verified successfully</div>
565
- <div class="medos-appointment-actions">
566
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
567
- <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>
568
800
  </div>
569
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>
570
806
  `;
571
807
  }
572
808
  return `
573
- <div class="medos-appointment-section">
574
- <label class="medos-appointment-label">Enter OTP</label>
575
- <input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.state.otpCode)}" maxlength="6" />
576
- <div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
577
- <div class="medos-appointment-actions">
578
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-change-number">Change Number</button>
579
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.state.otpCode.length !== 6 || this.state.otpVerifying
580
- ? "disabled"
581
- : ""} 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>
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>
582
827
  </div>
583
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>
834
+ </div>
584
835
  `;
585
836
  }
586
837
  renderStep4() {
@@ -592,97 +843,211 @@ class AppointmentCalendarWidget {
592
843
  this.state.patientZipcode &&
593
844
  this.state.otpVerified;
594
845
  return `
595
- <div class="medos-appointment-section">
596
- <label class="medos-appointment-label">Patient Name</label>
597
- <input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.state.patientName)}" />
598
- <label class="medos-appointment-label" style="margin-top: 12px">Age</label>
599
- <input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.state.patientAge)}" />
600
- <label class="medos-appointment-label" style="margin-top: 12px">Email (Optional)</label>
601
- <input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.state.patientEmail)}" />
602
- <label class="medos-appointment-label" style="margin-top: 12px">Gender (Optional)</label>
603
- <select class="medos-appointment-select" id="medos-patient-gender">
604
- <option value="">-- Select Gender --</option>
605
- <option value="MALE" ${this.state.patientGender === "MALE" ? "selected" : ""}>Male</option>
606
- <option value="FEMALE" ${this.state.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
607
- <option value="OTHER" ${this.state.patientGender === "OTHER" ? "selected" : ""}>Other</option>
608
- </select>
609
- <label class="medos-appointment-label" style="margin-top: 12px">Address Line 1 *</label>
610
- <input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.state.patientAddress)}" />
611
- <div class="medos-appointment-form-grid">
612
- <div>
613
- <label class="medos-appointment-label">City *</label>
614
- <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>
615
861
  </div>
616
- <div>
617
- <label class="medos-appointment-label">State *</label>
618
- <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>
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>
619
875
  </div>
620
876
  </div>
621
- <div class="medos-appointment-form-grid">
622
- <div>
623
- <label class="medos-appointment-label">Country *</label>
624
- <input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.state.patientCountry)}" />
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>
883
+ </div>
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)}" />
625
888
  </div>
626
- <div>
627
- <label class="medos-appointment-label">Zipcode *</label>
628
- <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)}" />
629
912
  </div>
630
- </div>
631
- <label class="medos-appointment-label" style="margin-top: 12px">Landmark (Optional)</label>
632
- <input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.state.patientLandmark)}" />
633
- <label class="medos-appointment-label" style="margin-top: 12px">Problem Facing</label>
634
- <textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.state.patientName)}</textarea>
635
- <div class="medos-appointment-actions">
636
- <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
637
- <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>
638
913
  </div>
639
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>
919
+ </div>
640
920
  `;
641
921
  }
922
+ renderSuccessIcon() {
923
+ return `
924
+ <div style="position: relative; display: inline-block;">
925
+ <svg width="64" height="64" viewBox="0 0 41 41" fill="none">
926
+ <path
927
+ d="M31.1309 4.90254C32.388 4.98797 33.0166 5.03069 33.5247 5.25288C34.2598 5.57438 34.8467 6.16126 35.1682 6.8964C35.3904 7.40445 35.4331 8.03302 35.5185 9.29016L35.7135 12.159C35.748 12.6674 35.7653 12.9217 35.8206 13.1645C35.9004 13.5154 36.0391 13.8503 36.2308 14.1549C36.3634 14.3657 36.531 14.5576 36.8661 14.9416L38.7568 17.108C39.5853 18.0574 39.9996 18.532 40.2017 19.0484C40.4942 19.7955 40.4942 20.6255 40.2017 21.3727C39.9996 21.889 39.5853 22.3637 38.7568 23.313L36.8661 25.4795C36.531 25.8634 36.3634 26.0554 36.2308 26.2662C36.0391 26.5708 35.9004 26.9056 35.8206 27.2566C35.7653 27.4994 35.748 27.7536 35.7135 28.2621L35.5185 31.1309C35.4331 32.388 35.3904 33.0166 35.1682 33.5247C34.8467 34.2598 34.2598 34.8467 33.5247 35.1682C33.0166 35.3904 32.388 35.4331 31.1309 35.5185L28.2621 35.7135C27.7536 35.748 27.4994 35.7653 27.2566 35.8206C26.9056 35.9004 26.5708 36.0391 26.2662 36.2308C26.0554 36.3634 25.8634 36.531 25.4795 36.8661L23.313 38.7568C22.3637 39.5853 21.889 39.9996 21.3727 40.2017C20.6255 40.4942 19.7955 40.4942 19.0484 40.2017C18.532 39.9996 18.0574 39.5853 17.108 38.7568L14.9416 36.8661C14.5576 36.531 14.3657 36.3634 14.1549 36.2308C13.8503 36.0391 13.5154 35.9004 13.1645 35.8206C12.9217 35.7653 12.6674 35.748 12.159 35.7135L9.29016 35.5185C8.03302 35.4331 7.40445 35.3904 6.8964 35.1682C6.16126 34.8467 5.57438 34.2598 5.25288 33.5247C5.03069 33.0166 4.98797 32.388 4.90254 31.1309L4.70759 28.2621C4.67304 27.7536 4.65576 27.4994 4.60049 27.2566C4.52063 26.9056 4.38193 26.5708 4.19028 26.2662C4.05764 26.0554 3.89009 25.8634 3.555 25.4795L1.66428 23.313C0.83576 22.3637 0.421499 21.889 0.219363 21.3727C-0.073121 20.6255 -0.0731209 19.7955 0.219363 19.0484C0.421499 18.532 0.83576 18.0574 1.66428 17.108L3.555 14.9416C3.89009 14.5576 4.05764 14.3657 4.19027 14.1549C4.38193 13.8503 4.52063 13.5154 4.60049 13.1645C4.65576 12.9217 4.67304 12.6674 4.70759 12.159L4.90254 9.29016C4.98797 8.03302 5.03069 7.40445 5.25288 6.8964C5.57438 6.16126 6.16126 5.57438 6.8964 5.25288C7.40445 5.03069 8.03302 4.98797 9.29016 4.90254L12.159 4.70759C12.6674 4.67304 12.9217 4.65577 13.1645 4.6005C13.5154 4.52063 13.8503 4.38193 14.1549 4.19028C14.3657 4.05764 14.5576 3.89009 14.9416 3.555L17.108 1.66428C18.0574 0.83576 18.532 0.421499 19.0484 0.219363C19.7955 -0.073121 20.6255 -0.073121 21.3727 0.219363C21.889 0.421499 22.3637 0.83576 23.313 1.66428L25.4795 3.555C25.8634 3.89009 26.0554 4.05764 26.2662 4.19028C26.5708 4.38193 26.9056 4.52063 27.2566 4.6005C27.4994 4.65577 27.7536 4.67304 28.2621 4.70759L31.1309 4.90254Z"
928
+ fill="#006E0F"
929
+ />
930
+ </svg>
931
+ <svg width="16" height="12" viewBox="0 0 16 12" fill="none" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);">
932
+ <path
933
+ d="M5.472 11.544L0 6.072L1.368 4.704L5.472 8.808L14.28 0L15.648 1.368L5.472 11.544Z"
934
+ fill="white"
935
+ />
936
+ </svg>
937
+ </div>
938
+ `;
939
+ }
940
+ formatDate(dateStr) {
941
+ try {
942
+ const date = new Date(dateStr);
943
+ return date.toLocaleDateString("en-US", {
944
+ weekday: "long",
945
+ year: "numeric",
946
+ month: "long",
947
+ day: "numeric",
948
+ });
949
+ }
950
+ catch {
951
+ return dateStr;
952
+ }
953
+ }
954
+ formatTime(timeStr) {
955
+ try {
956
+ const time = new Date(`2000-01-01T${timeStr}`);
957
+ return time.toLocaleTimeString("en-US", {
958
+ hour: "numeric",
959
+ minute: "2-digit",
960
+ hour12: true,
961
+ });
962
+ }
963
+ catch {
964
+ return timeStr;
965
+ }
966
+ }
967
+ calculateDuration() {
968
+ if (this.state.selectedSlot) {
969
+ const start = new Date(this.state.selectedSlot.start);
970
+ const end = new Date(this.state.selectedSlot.end);
971
+ const diffMs = end.getTime() - start.getTime();
972
+ return Math.round(diffMs / (1000 * 60));
973
+ }
974
+ return 60;
975
+ }
642
976
  renderStep5() {
977
+ const duration = this.calculateDuration();
978
+ const selectedAddress = this.state.addresses.find((addr) => addr.id === this.state.selectedAddress);
979
+ const appointmentDate = this.state.selectedDate
980
+ ? this.formatDate(formatDateToISO(this.state.selectedDate))
981
+ : "";
982
+ const startTime = this.state.selectedSlot?.start
983
+ ? this.formatTime(new Date(this.state.selectedSlot.start).toTimeString().slice(0, 5))
984
+ : "";
643
985
  return `
644
- <div class="medos-appointment-section" style="text-align: center">
645
- <div class="medos-appointment-success-card">
646
- <div class="medos-appointment-success-icon">✓</div>
647
- <div class="medos-appointment-success-title">Appointment Confirmed</div>
648
- <div class="medos-appointment-small-muted">Thank you, ${this.escapeHtml(this.state.patientName || "Patient")}. Your appointment is confirmed.</div>
986
+ <div style="display: flex; flex-direction: column; padding: 0; font-family: Arial, sans-serif; background: #f8f9fa; min-height: 500px;">
987
+ <!-- Header with border -->
988
+ <div style="padding: 20px 24px; font-size: 24px; font-weight: bold; color: #1a365d; border-bottom: 2px solid #e2e8f0; background: white;">
989
+ Appointment Confirmed
990
+ </div>
991
+
992
+ <!-- Main content with border -->
993
+ <div style="flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 40px 24px; border: 2px solid #e2e8f0; border-top: none; background: white; text-align: center;">
994
+ <!-- Success Title -->
995
+ <h2 style="font-size: 20px; font-weight: 600; color: #006E0F; margin: 0 0 24px 0;">
996
+ Appointment Confirmed
997
+ </h2>
998
+
999
+ <!-- Success Icon -->
1000
+ <div style="margin-bottom: 32px;">
1001
+ ${this.renderSuccessIcon()}
1002
+ </div>
1003
+
1004
+ <!-- Appointment Details -->
1005
+ <div style="width: 100%; max-width: 500px; margin-bottom: 32px;">
1006
+ <h3 style="font-size: 18px; font-weight: 600; color: #006E0F; margin-bottom: 24px;">
1007
+ Appointment Details
1008
+ </h3>
1009
+
1010
+ <div style="display: flex; flex-direction: column; gap: 12px; font-size: 16px; line-height: 1.6; color: #4a5568;">
1011
+ <div><strong style="color: #006E0F;">Patient:</strong> ${this.escapeHtml(this.state.patientName || "Patient")}</div>
1012
+ <div><strong style="color: #006E0F;">Visitation Type:</strong> ${this.state.consultationMode === "ONLINE"
1013
+ ? "Online Consultation"
1014
+ : "General"}</div>
1015
+ ${appointmentDate
1016
+ ? `<div><strong style="color: #006E0F;">Date:</strong> ${appointmentDate}</div>`
1017
+ : ""}
1018
+ ${startTime
1019
+ ? `<div><strong style="color: #006E0F;">Time:</strong> ${startTime}</div>`
1020
+ : ""}
1021
+ <div><strong style="color: #006E0F;">Duration:</strong> ~${duration} minutes</div>
1022
+ ${selectedAddress?.label
1023
+ ? `<div><strong style="color: #006E0F;">Location:</strong> ${this.escapeHtml(selectedAddress.label)}</div>`
1024
+ : ""}
1025
+ </div>
649
1026
  </div>
650
- <div style="margin-top: 14px; display: flex; justify-content: center">
651
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-book-another" style="width: 160px">Book Another</button>
1027
+
1028
+ <!-- Confirmation Message -->
1029
+ <div style="font-size: 16px; font-style: italic; color: #718096; text-align: center; max-width: 600px; line-height: 1.5;">
1030
+ A confirmation email has been sent to the Patient's Email address.
652
1031
  </div>
653
1032
  </div>
654
- `;
1033
+ </div>
1034
+ `;
655
1035
  }
656
1036
  attachEventListeners() {
657
- const addressSelect = this.container.querySelector("#medos-address-select");
658
- if (addressSelect) {
659
- addressSelect.addEventListener("change", (e) => {
660
- const target = e.target;
661
- this.handleAddressChange(Number(target.value) || null);
662
- });
663
- }
664
- const doctorSelect = this.container.querySelector("#medos-doctor-select");
665
- if (doctorSelect) {
666
- doctorSelect.addEventListener("change", (e) => {
667
- const target = e.target;
668
- this.state.selectedDoctor = Number(target.value) || null;
669
- this.render();
670
- });
671
- }
672
- const dateInput = this.container.querySelector("#medos-date-input");
673
- if (dateInput) {
674
- dateInput.addEventListener("change", (e) => {
1037
+ const consultationRadios = this.container.querySelectorAll('input[name="consultationMode"]');
1038
+ consultationRadios.forEach((radio) => {
1039
+ radio.addEventListener("change", (e) => {
675
1040
  const target = e.target;
676
- this.state.selectedDate = new Date(target.value);
1041
+ this.state.consultationMode = target.value;
677
1042
  this.render();
678
1043
  });
679
- }
680
- const slotCards = this.container.querySelectorAll(".medos-appointment-slot-card");
1044
+ });
1045
+ const slotCards = this.container.querySelectorAll(".medos-slot-card");
681
1046
  slotCards.forEach((card) => {
682
1047
  card.addEventListener("click", () => {
683
- const slotId = card.getAttribute("data-slot-id");
684
- const slotStart = card.getAttribute("data-slot-start");
685
- 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;
686
1051
  if (slotStart && slotEnd) {
687
1052
  this.state.selectedSlot = {
688
1053
  start: slotStart,
@@ -704,7 +1069,11 @@ class AppointmentCalendarWidget {
704
1069
  value = value.replace(/[^\d+]/g, "");
705
1070
  this.state.countryCode = value;
706
1071
  target.value = value;
707
- 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
+ }
708
1077
  });
709
1078
  }
710
1079
  const phoneInput = this.container.querySelector("#medos-phone");
@@ -713,7 +1082,11 @@ class AppointmentCalendarWidget {
713
1082
  const target = e.target;
714
1083
  this.state.patientPhone = target.value.replace(/\D/g, "");
715
1084
  target.value = this.state.patientPhone;
716
- 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
+ }
717
1090
  });
718
1091
  }
719
1092
  const otpInput = this.container.querySelector("#medos-otp");
@@ -721,7 +1094,10 @@ class AppointmentCalendarWidget {
721
1094
  otpInput.addEventListener("input", (e) => {
722
1095
  const target = e.target;
723
1096
  this.state.otpCode = target.value;
724
- 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
+ }
725
1101
  });
726
1102
  }
727
1103
  const patientNameInput = this.container.querySelector("#medos-patient-name");
@@ -729,6 +1105,7 @@ class AppointmentCalendarWidget {
729
1105
  patientNameInput.addEventListener("input", (e) => {
730
1106
  const target = e.target;
731
1107
  this.state.patientName = target.value;
1108
+ this.updateSubmitButtonState();
732
1109
  });
733
1110
  }
734
1111
  const patientAgeInput = this.container.querySelector("#medos-patient-age");
@@ -736,6 +1113,7 @@ class AppointmentCalendarWidget {
736
1113
  patientAgeInput.addEventListener("input", (e) => {
737
1114
  const target = e.target;
738
1115
  this.state.patientAge = target.value;
1116
+ this.updateSubmitButtonState();
739
1117
  });
740
1118
  }
741
1119
  const patientEmailInput = this.container.querySelector("#medos-patient-email");
@@ -743,6 +1121,7 @@ class AppointmentCalendarWidget {
743
1121
  patientEmailInput.addEventListener("input", (e) => {
744
1122
  const target = e.target;
745
1123
  this.state.patientEmail = target.value;
1124
+ this.updateSubmitButtonState();
746
1125
  });
747
1126
  }
748
1127
  const patientGenderSelect = this.container.querySelector("#medos-patient-gender");
@@ -757,6 +1136,7 @@ class AppointmentCalendarWidget {
757
1136
  patientAddressInput.addEventListener("input", (e) => {
758
1137
  const target = e.target;
759
1138
  this.state.patientAddress = target.value;
1139
+ this.updateSubmitButtonState();
760
1140
  });
761
1141
  }
762
1142
  const patientCityInput = this.container.querySelector("#medos-patient-city");
@@ -764,6 +1144,7 @@ class AppointmentCalendarWidget {
764
1144
  patientCityInput.addEventListener("input", (e) => {
765
1145
  const target = e.target;
766
1146
  this.state.patientCity = target.value;
1147
+ this.updateSubmitButtonState();
767
1148
  });
768
1149
  }
769
1150
  const patientStateInput = this.container.querySelector("#medos-patient-state");
@@ -771,6 +1152,7 @@ class AppointmentCalendarWidget {
771
1152
  patientStateInput.addEventListener("input", (e) => {
772
1153
  const target = e.target;
773
1154
  this.state.patientState = target.value;
1155
+ this.updateSubmitButtonState();
774
1156
  });
775
1157
  }
776
1158
  const patientCountryInput = this.container.querySelector("#medos-patient-country");
@@ -778,6 +1160,7 @@ class AppointmentCalendarWidget {
778
1160
  patientCountryInput.addEventListener("input", (e) => {
779
1161
  const target = e.target;
780
1162
  this.state.patientCountry = target.value;
1163
+ this.updateSubmitButtonState();
781
1164
  });
782
1165
  }
783
1166
  const patientZipcodeInput = this.container.querySelector("#medos-patient-zipcode");
@@ -785,6 +1168,7 @@ class AppointmentCalendarWidget {
785
1168
  patientZipcodeInput.addEventListener("input", (e) => {
786
1169
  const target = e.target;
787
1170
  this.state.patientZipcode = target.value;
1171
+ this.updateSubmitButtonState();
788
1172
  });
789
1173
  }
790
1174
  const patientLandmarkInput = this.container.querySelector("#medos-patient-landmark");
@@ -796,7 +1180,11 @@ class AppointmentCalendarWidget {
796
1180
  }
797
1181
  const nextBtn = this.container.querySelector("#medos-btn-next");
798
1182
  if (nextBtn) {
799
- nextBtn.addEventListener("click", () => this.goToNext());
1183
+ nextBtn.addEventListener("click", () => {
1184
+ if (!nextBtn.disabled) {
1185
+ this.goToNext();
1186
+ }
1187
+ });
800
1188
  }
801
1189
  const backBtn = this.container.querySelector("#medos-btn-back");
802
1190
  if (backBtn) {
@@ -804,11 +1192,19 @@ class AppointmentCalendarWidget {
804
1192
  }
805
1193
  const sendOtpBtn = this.container.querySelector("#medos-btn-send-otp");
806
1194
  if (sendOtpBtn) {
807
- sendOtpBtn.addEventListener("click", () => this.sendOtp());
1195
+ sendOtpBtn.addEventListener("click", () => {
1196
+ if (!sendOtpBtn.disabled) {
1197
+ this.sendOtp();
1198
+ }
1199
+ });
808
1200
  }
809
1201
  const verifyOtpBtn = this.container.querySelector("#medos-btn-verify-otp");
810
1202
  if (verifyOtpBtn) {
811
- verifyOtpBtn.addEventListener("click", () => this.verifyOtp());
1203
+ verifyOtpBtn.addEventListener("click", () => {
1204
+ if (!verifyOtpBtn.disabled) {
1205
+ this.verifyOtp();
1206
+ }
1207
+ });
812
1208
  }
813
1209
  const changeNumberBtn = this.container.querySelector("#medos-btn-change-number");
814
1210
  if (changeNumberBtn) {
@@ -817,9 +1213,19 @@ class AppointmentCalendarWidget {
817
1213
  this.render();
818
1214
  });
819
1215
  }
1216
+ const resendOtpBtn = this.container.querySelector("#medos-btn-resend-otp");
1217
+ if (resendOtpBtn) {
1218
+ resendOtpBtn.addEventListener("click", () => {
1219
+ this.sendOtp();
1220
+ });
1221
+ }
820
1222
  const submitBtn = this.container.querySelector("#medos-btn-submit");
821
1223
  if (submitBtn) {
822
- submitBtn.addEventListener("click", () => this.submitAppointment());
1224
+ submitBtn.addEventListener("click", () => {
1225
+ if (!submitBtn.disabled) {
1226
+ this.submitAppointment();
1227
+ }
1228
+ });
823
1229
  }
824
1230
  const bookAnotherBtn = this.container.querySelector("#medos-btn-book-another");
825
1231
  if (bookAnotherBtn) {