medos-sdk 1.0.2 → 1.0.3

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 (94) hide show
  1. package/dist/client/MedosClient.d.ts +1 -1
  2. package/dist/client/MedosClient.js +1 -1
  3. package/dist/components/AppointmentCalender.d.ts +1 -4
  4. package/dist/components/AppointmentCalender.js +282 -530
  5. package/dist/components/AppointmentDateTimeModal.d.ts +14 -0
  6. package/dist/components/AppointmentDateTimeModal.js +206 -0
  7. package/dist/components/ConfigurableCard.d.ts +12 -0
  8. package/dist/components/ConfigurableCard.js +29 -0
  9. package/dist/components/DoctorSelectModal.d.ts +7 -0
  10. package/dist/components/DoctorSelectModal.js +80 -0
  11. package/dist/components/Icons/Check.d.ts +6 -0
  12. package/dist/components/Icons/Check.js +2 -0
  13. package/dist/components/Icons/ChevronDownIcon.d.ts +4 -0
  14. package/dist/components/Icons/ChevronDownIcon.js +2 -0
  15. package/dist/components/Icons/ChevronLeft.d.ts +3 -0
  16. package/dist/components/Icons/ChevronLeft.js +3 -0
  17. package/dist/components/Icons/ChevronRight.d.ts +3 -0
  18. package/dist/components/Icons/ChevronRight.js +3 -0
  19. package/dist/components/Icons/ConfirmationCheck.d.ts +1 -0
  20. package/dist/components/Icons/ConfirmationCheck.js +9 -0
  21. package/dist/components/Icons/ConsultationType.d.ts +1 -0
  22. package/dist/components/Icons/ConsultationType.js +2 -0
  23. package/dist/components/Icons/Date&TimeIcon.d.ts +1 -0
  24. package/dist/components/Icons/Date&TimeIcon.js +2 -0
  25. package/dist/components/Icons/MapIcon.d.ts +1 -0
  26. package/dist/components/Icons/MapIcon.js +2 -0
  27. package/dist/components/Icons/PaymentMethodIcon.d.ts +1 -0
  28. package/dist/components/Icons/PaymentMethodIcon.js +2 -0
  29. package/dist/components/Icons/UserIcon.d.ts +1 -0
  30. package/dist/components/Icons/UserIcon.js +2 -0
  31. package/dist/components/PatientDetailsStep.d.ts +3 -0
  32. package/dist/components/PatientDetailsStep.js +76 -0
  33. package/dist/components/PhoneVerificationStep.d.ts +3 -0
  34. package/dist/components/PhoneVerificationStep.js +39 -0
  35. package/dist/components/SuccessStep.d.ts +3 -0
  36. package/dist/components/SuccessStep.js +17 -0
  37. package/dist/components/custom-calendar.d.ts +5 -0
  38. package/dist/components/custom-calendar.js +153 -0
  39. package/dist/components/styles.d.ts +6 -0
  40. package/dist/components/styles.js +257 -0
  41. package/dist/components/types.d.ts +182 -0
  42. package/dist/components/types.js +55 -0
  43. package/dist/components/ui/select.d.ts +10 -0
  44. package/dist/components/ui/select.js +21 -0
  45. package/dist/components/uiComponents/SelectDropdown.d.ts +41 -0
  46. package/dist/components/uiComponents/SelectDropdown.js +302 -0
  47. package/dist/components/utils.d.ts +5 -0
  48. package/dist/components/utils.js +15 -0
  49. package/dist/components/validation.d.ts +2 -0
  50. package/dist/components/validation.js +7 -0
  51. package/dist/context/TemplateContext.d.ts +12 -0
  52. package/dist/context/TemplateContext.js +19 -0
  53. package/dist/lib/templateUtils.d.ts +3 -0
  54. package/dist/lib/templateUtils.js +28 -0
  55. package/dist/services/AppointmentService.d.ts +4 -5
  56. package/dist/services/AppointmentService.js +12 -10
  57. package/dist/templates/registry.d.ts +12 -0
  58. package/dist/templates/registry.js +58 -0
  59. package/dist/vanilla/AppointmentCalendarWidget.d.ts +2 -34
  60. package/dist/vanilla/AppointmentCalendarWidget.js +264 -273
  61. package/dist/vanilla/client/MedosClient.d.ts +1 -1
  62. package/dist/vanilla/components/AppointmentCalender.d.ts +1 -4
  63. package/dist/vanilla/components/AppointmentDateTimeModal.d.ts +14 -0
  64. package/dist/vanilla/components/ConfigurableCard.d.ts +12 -0
  65. package/dist/vanilla/components/DoctorSelectModal.d.ts +7 -0
  66. package/dist/vanilla/components/Icons/Check.d.ts +6 -0
  67. package/dist/vanilla/components/Icons/ChevronDownIcon.d.ts +4 -0
  68. package/dist/vanilla/components/Icons/ChevronLeft.d.ts +3 -0
  69. package/dist/vanilla/components/Icons/ChevronRight.d.ts +3 -0
  70. package/dist/vanilla/components/Icons/ConfirmationCheck.d.ts +1 -0
  71. package/dist/vanilla/components/Icons/ConsultationType.d.ts +1 -0
  72. package/dist/vanilla/components/Icons/Date&TimeIcon.d.ts +1 -0
  73. package/dist/vanilla/components/Icons/MapIcon.d.ts +1 -0
  74. package/dist/vanilla/components/Icons/PaymentMethodIcon.d.ts +1 -0
  75. package/dist/vanilla/components/Icons/UserIcon.d.ts +1 -0
  76. package/dist/vanilla/components/PatientDetailsStep.d.ts +3 -0
  77. package/dist/vanilla/components/PhoneVerificationStep.d.ts +3 -0
  78. package/dist/vanilla/components/SuccessStep.d.ts +3 -0
  79. package/dist/vanilla/components/custom-calendar.d.ts +5 -0
  80. package/dist/vanilla/components/styles.d.ts +6 -0
  81. package/dist/vanilla/components/types.d.ts +182 -0
  82. package/dist/vanilla/components/ui/select.d.ts +10 -0
  83. package/dist/vanilla/components/uiComponents/SelectDropdown.d.ts +41 -0
  84. package/dist/vanilla/components/utils.d.ts +5 -0
  85. package/dist/vanilla/components/validation.d.ts +2 -0
  86. package/dist/vanilla/context/TemplateContext.d.ts +12 -0
  87. package/dist/vanilla/lib/templateUtils.d.ts +3 -0
  88. package/dist/vanilla/services/AppointmentService.d.ts +4 -5
  89. package/dist/vanilla/templates/alternative.css +13 -0
  90. package/dist/vanilla/templates/default.css +13 -0
  91. package/dist/vanilla/templates/registry.d.ts +12 -0
  92. package/dist/vanilla/vanilla/AppointmentCalendarWidget.d.ts +2 -34
  93. package/dist/vanilla/widget.js +331 -283
  94. package/package.json +9 -4
@@ -1,40 +1,13 @@
1
- import { AppointmentService } from "../services/AppointmentService";
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";
5
+ import { validatePhoneNumber, validateCountryCode, } from "../components/validation";
6
+ import { formatDateToISO, parsePatientName } from "../components/utils";
4
7
  class AppointmentCalendarWidget {
5
8
  constructor(container, options) {
6
9
  this.mounted = true;
7
- this.step = 0;
8
- this.addresses = [];
9
- this.addressDoctorsMap = {};
10
- this.selectedAddress = null;
11
- this.workspaceId = null;
12
10
  this.doctors = [];
13
- this.selectedDoctor = null;
14
- this.date = "";
15
- this.slots = [];
16
- this.selectedSlot = null;
17
- this.loading = false;
18
- this.error = null;
19
- this.patientName = "";
20
- this.patientAge = "";
21
- this.patientAddress = "";
22
- this.patientCity = "";
23
- this.patientState = "";
24
- this.patientCountry = "";
25
- this.patientZipcode = "";
26
- this.patientLandmark = "";
27
- this.patientEmail = "";
28
- this.patientGender = "";
29
- this.problemFacing = "";
30
- this.consultationCharge = "";
31
- this.countryCode = "+91";
32
- this.patientPhone = "";
33
- this.otpCode = "";
34
- this.otpSent = false;
35
- this.otpVerified = false;
36
- this.otpSending = false;
37
- this.otpVerifying = false;
38
11
  if (typeof container === "string") {
39
12
  const el = document.getElementById(container);
40
13
  if (!el) {
@@ -46,6 +19,7 @@ class AppointmentCalendarWidget {
46
19
  this.container = container;
47
20
  }
48
21
  this.options = options;
22
+ this.state = { ...INITIAL_STATE };
49
23
  this.init();
50
24
  }
51
25
  async init() {
@@ -68,19 +42,18 @@ class AppointmentCalendarWidget {
68
42
  this.render();
69
43
  }
70
44
  async loadAddresses() {
71
- this.setLoading(true);
72
- this.setError(null);
45
+ this.setState({ loading: true, error: null });
73
46
  try {
74
47
  const addrResp = await AppointmentService.getAddresses();
75
48
  if (addrResp && Array.isArray(addrResp.addresses)) {
76
49
  const fetchedAddresses = addrResp.addresses;
77
50
  if (addrResp.workspaceId) {
78
- this.workspaceId = addrResp.workspaceId;
51
+ this.state.workspaceId = addrResp.workspaceId;
79
52
  }
80
53
  if (fetchedAddresses.length > 0) {
81
54
  const addrMap = {};
82
55
  const mappedAddrs = fetchedAddresses.map((a, idx) => {
83
- const id = String(a.id ?? idx);
56
+ const id = Number(a.id ?? idx);
84
57
  const label = a.completeAddress ?? a.label ?? a.address ?? `Address ${idx + 1}`;
85
58
  const docs = Array.isArray(a.doctors)
86
59
  ? a.doctors
@@ -88,233 +61,242 @@ class AppointmentCalendarWidget {
88
61
  addrMap[id] = docs || [];
89
62
  return { id, label };
90
63
  });
91
- this.addresses = mappedAddrs;
92
- this.addressDoctorsMap = addrMap;
64
+ this.state.addresses = mappedAddrs;
65
+ this.state.addressDoctorsMap = addrMap;
93
66
  const anyDoctorsExist = Object.values(addrMap).some((arr) => Array.isArray(arr) && arr.length > 0);
94
67
  if (mappedAddrs.length === 1) {
95
68
  const only = mappedAddrs[0];
96
- this.selectedAddress = only.id;
69
+ this.state.selectedAddress = only.id;
97
70
  const docsForAddr = addrMap[only.id] || [];
98
71
  if (docsForAddr.length > 0) {
99
72
  this.doctors = docsForAddr;
100
73
  if (docsForAddr.length === 1) {
101
- this.selectedDoctor = docsForAddr[0].id;
102
- this.step = 1;
74
+ this.state.selectedDoctor = docsForAddr[0].id;
75
+ this.state.step = 1;
103
76
  }
104
77
  else {
105
- this.step = 0;
78
+ this.state.step = 0;
106
79
  }
107
80
  }
108
81
  else {
109
82
  if (anyDoctorsExist) {
110
- this.setError("No doctors at this address. Please choose a different address.");
83
+ this.setState({
84
+ error: "No doctors at this address. Please choose a different address.",
85
+ });
111
86
  this.doctors = [];
112
- this.step = 0;
87
+ this.state.step = 0;
113
88
  }
114
89
  else {
115
- this.setError("No doctors available for the selected location(s).");
90
+ this.setState({
91
+ error: "No doctors available for the selected location(s).",
92
+ });
116
93
  this.doctors = [];
117
- this.step = 0;
94
+ this.state.step = 0;
118
95
  }
119
96
  }
120
97
  }
121
98
  else {
122
- this.step = 0;
99
+ this.state.step = 0;
123
100
  }
124
101
  }
125
102
  else {
126
- this.setError("No addresses or doctors available.");
127
- this.addresses = [];
128
- this.addressDoctorsMap = {};
103
+ this.setState({ error: "No addresses or doctors available." });
104
+ this.state.addresses = [];
105
+ this.state.addressDoctorsMap = {};
129
106
  this.doctors = [];
130
- this.step = 0;
107
+ this.state.step = 0;
131
108
  }
132
109
  }
133
110
  }
134
111
  catch (e) {
135
112
  const msg = e.message || "Failed to load addresses";
136
- this.setError(msg);
113
+ this.setState({ error: msg });
137
114
  this.options.onError?.(e);
138
115
  }
139
116
  finally {
140
- this.setLoading(false);
117
+ this.setState({ loading: false });
141
118
  }
142
119
  }
143
120
  async handleAddressChange(addressId) {
144
- this.selectedAddress = addressId;
121
+ this.state.selectedAddress = addressId;
145
122
  if (!addressId) {
146
123
  this.doctors = [];
147
- this.selectedDoctor = null;
124
+ this.state.selectedDoctor = null;
148
125
  this.render();
149
126
  return;
150
127
  }
151
- this.setLoading(true);
152
- this.setError(null);
128
+ this.setState({ loading: true, error: null });
153
129
  try {
154
- const docsForAddr = this.addressDoctorsMap[addressId] ?? [];
130
+ const docsForAddr = this.state.addressDoctorsMap[addressId] ?? [];
155
131
  if (docsForAddr.length > 0) {
156
132
  this.doctors = docsForAddr;
157
133
  if (docsForAddr.length === 1) {
158
- this.selectedDoctor = docsForAddr[0].id;
159
- this.step = 1;
134
+ this.state.selectedDoctor = docsForAddr[0].id;
135
+ this.state.step = 1;
160
136
  }
161
137
  else {
162
- this.selectedDoctor = null;
138
+ this.state.selectedDoctor = null;
163
139
  }
164
140
  }
165
141
  else {
166
- const otherHasDoctors = Object.entries(this.addressDoctorsMap).some(([key, docs]) => key !== addressId && Array.isArray(docs) && docs.length > 0);
142
+ const otherHasDoctors = Object.entries(this.state.addressDoctorsMap).some(([key, docs]) => Number(key) !== addressId && Array.isArray(docs) && docs.length > 0);
167
143
  this.doctors = [];
168
- this.selectedDoctor = null;
144
+ this.state.selectedDoctor = null;
169
145
  if (otherHasDoctors) {
170
- this.setError("No doctors at this address. Please select a different address.");
146
+ this.setState({
147
+ error: "No doctors at this address. Please select a different address.",
148
+ });
171
149
  }
172
150
  else {
173
- this.setError("No doctors available for the selected location(s).");
151
+ this.setState({
152
+ error: "No doctors available for the selected location(s).",
153
+ });
174
154
  }
175
155
  }
176
156
  }
177
157
  catch (e) {
178
- this.setError(e.message || "Failed to load doctors for address");
158
+ this.setState({
159
+ error: e.message || "Failed to load doctors for address",
160
+ });
179
161
  }
180
162
  finally {
181
- this.setLoading(false);
163
+ this.setState({ loading: false });
182
164
  this.render();
183
165
  }
184
166
  }
185
167
  async loadSlots() {
186
- if (!this.workspaceId || !this.selectedAddress || !this.selectedDoctor || !this.date) {
187
- this.slots = [];
188
- this.selectedSlot = null;
168
+ const dateStr = formatDateToISO(this.state.selectedDate);
169
+ if (!this.state.workspaceId ||
170
+ !this.state.selectedAddress ||
171
+ !this.state.selectedDoctor ||
172
+ !dateStr) {
173
+ this.state.slots = [];
174
+ this.state.selectedSlot = null;
189
175
  this.render();
190
176
  return;
191
177
  }
192
- this.setLoading(true);
193
- this.setError(null);
178
+ this.setState({ loading: true, error: null });
194
179
  try {
195
- const s = await AppointmentService.fetchSlots(this.workspaceId, this.selectedAddress, this.selectedDoctor, this.date);
196
- this.slots = s || [];
180
+ const s = await AppointmentService.fetchSlots(this.state.workspaceId, this.state.selectedAddress, this.state.selectedDoctor, dateStr);
181
+ this.state.slots = s || [];
197
182
  }
198
183
  catch (e) {
199
- this.setError(e.message || "Failed to load slots");
184
+ this.setState({ error: e.message || "Failed to load slots" });
200
185
  }
201
186
  finally {
202
- this.setLoading(false);
187
+ this.setState({ loading: false });
203
188
  this.render();
204
189
  }
205
190
  }
206
- validatePhoneNumber(phone) {
207
- const cleaned = phone.replace(/\D/g, "");
208
- return cleaned.length >= 7 && cleaned.length <= 15;
209
- }
210
- validateCountryCode(code) {
211
- return /^\+[1-9]\d{0,3}$/.test(code);
212
- }
213
191
  canProceedFromMergedStep() {
214
- if (this.addresses.length === 0 || this.doctors.length === 0)
192
+ if (this.state.addresses.length === 0 || this.doctors.length === 0)
215
193
  return false;
216
- if (this.addresses.length > 1 && !this.selectedAddress)
194
+ if (this.state.addresses.length > 1 && !this.state.selectedAddress)
217
195
  return false;
218
- if (this.doctors.length > 1 && !this.selectedDoctor)
196
+ if (this.doctors.length > 1 && !this.state.selectedDoctor)
219
197
  return false;
220
198
  return true;
221
199
  }
222
200
  async sendOtp() {
223
- this.setError(null);
224
- if (!this.countryCode) {
225
- this.setError("Please enter country code.");
201
+ this.setState({ error: null });
202
+ if (!this.state.countryCode) {
203
+ this.setState({ error: "Please enter country code." });
226
204
  return;
227
205
  }
228
- if (!this.validateCountryCode(this.countryCode)) {
229
- this.setError("Please enter a valid country code (e.g., +91, +1).");
206
+ if (!validateCountryCode(this.state.countryCode)) {
207
+ this.setState({
208
+ error: "Please enter a valid country code (e.g., +91, +1).",
209
+ });
230
210
  return;
231
211
  }
232
- if (!this.patientPhone) {
233
- this.setError("Please enter phone number.");
212
+ if (!this.state.patientPhone) {
213
+ this.setState({ error: "Please enter phone number." });
234
214
  return;
235
215
  }
236
- if (!this.validatePhoneNumber(this.patientPhone)) {
237
- this.setError("Please enter a valid phone number (7-15 digits).");
216
+ if (!validatePhoneNumber(this.state.patientPhone)) {
217
+ this.setState({
218
+ error: "Please enter a valid phone number (7-15 digits).",
219
+ });
238
220
  return;
239
221
  }
240
- this.otpSending = true;
222
+ this.setState({ otpSending: true });
241
223
  this.render();
242
224
  try {
243
225
  await PatientService.sendPhoneVerificationOtp({
244
- countryCode: this.countryCode,
245
- phoneNumber: this.patientPhone,
226
+ countryCode: this.state.countryCode,
227
+ phoneNumber: this.state.patientPhone,
246
228
  });
247
- this.otpSent = true;
248
- this.setError(null);
229
+ this.setState({ otpSent: true, error: null });
249
230
  }
250
231
  catch (e) {
251
232
  const msg = e.message || "Failed to send OTP";
252
- this.setError(msg);
233
+ this.setState({ error: msg });
253
234
  this.options.onError?.(e);
254
235
  }
255
236
  finally {
256
- this.otpSending = false;
237
+ this.setState({ otpSending: false });
257
238
  this.render();
258
239
  }
259
240
  }
260
241
  async verifyOtp() {
261
- this.setError(null);
262
- if (!this.countryCode || !this.patientPhone || !this.otpCode) {
263
- this.setError("Please enter all required fields.");
242
+ this.setState({ error: null });
243
+ if (!this.state.countryCode ||
244
+ !this.state.patientPhone ||
245
+ !this.state.otpCode) {
246
+ this.setState({ error: "Please enter all required fields." });
264
247
  return;
265
248
  }
266
- if (this.otpCode.length !== 6) {
267
- this.setError("Please enter a 6-digit OTP code.");
249
+ if (this.state.otpCode.length !== 6) {
250
+ this.setState({ error: "Please enter a 6-digit OTP code." });
268
251
  return;
269
252
  }
270
- this.otpVerifying = true;
253
+ this.setState({ otpVerifying: true });
271
254
  this.render();
272
255
  try {
273
256
  await PatientService.verifyPhoneVerificationOtp({
274
- countryCode: this.countryCode,
275
- phoneNumber: this.patientPhone,
276
- otpCode: this.otpCode,
257
+ countryCode: this.state.countryCode,
258
+ phoneNumber: this.state.patientPhone,
259
+ otpCode: this.state.otpCode,
277
260
  });
278
- this.otpVerified = true;
279
- this.setError(null);
261
+ this.setState({ otpVerified: true, error: null });
280
262
  }
281
263
  catch (e) {
282
264
  const msg = e.message || "Invalid OTP code";
283
- this.setError(msg);
265
+ this.setState({ error: msg });
284
266
  this.options.onError?.(e);
285
267
  }
286
268
  finally {
287
- this.otpVerifying = false;
269
+ this.setState({ otpVerifying: false });
288
270
  this.render();
289
271
  }
290
272
  }
291
273
  async submitAppointment() {
292
- this.setError(null);
293
- if (!this.selectedDoctor ||
294
- !this.selectedSlot ||
295
- !this.workspaceId ||
296
- !this.selectedAddress ||
297
- !this.patientAddress ||
298
- !this.patientCity ||
299
- !this.patientState ||
300
- !this.patientCountry ||
301
- !this.patientZipcode) {
302
- this.setError("Please ensure all required fields are complete.");
274
+ this.setState({ error: null });
275
+ if (!this.state.selectedDoctor ||
276
+ !this.state.selectedSlot ||
277
+ !this.state.workspaceId ||
278
+ !this.state.selectedAddress ||
279
+ !this.state.patientAddress ||
280
+ !this.state.patientCity ||
281
+ !this.state.patientState ||
282
+ !this.state.patientCountry ||
283
+ !this.state.patientZipcode) {
284
+ this.setState({
285
+ error: "Please ensure all required fields are complete.",
286
+ });
303
287
  return;
304
288
  }
305
- if (!this.otpVerified) {
306
- this.setError("Please verify your phone number first.");
289
+ if (!this.state.otpVerified) {
290
+ this.setState({ error: "Please verify your phone number first." });
307
291
  return;
308
292
  }
309
- this.setLoading(true);
293
+ this.setState({ loading: true });
310
294
  this.render();
311
295
  try {
312
- const nameParts = (this.patientName || "Patient").trim().split(/\s+/);
313
- const firstName = nameParts[0] || "Patient";
314
- const lastName = nameParts.slice(1).join(" ") || "";
315
- const startDate = new Date(this.selectedSlot.start);
316
- const endDate = new Date(this.selectedSlot.end);
317
- const appointmentDate = startDate.toISOString().split("T")[0];
296
+ const { firstName, lastName } = parsePatientName(this.state.patientName || "Patient");
297
+ const startDate = new Date(this.state.selectedSlot.start);
298
+ const endDate = new Date(this.state.selectedSlot.end);
299
+ const appointmentDate = formatDateToISO(startDate);
318
300
  const formatTime = (date) => {
319
301
  const hours = String(date.getHours()).padStart(2, "0");
320
302
  const minutes = String(date.getMinutes()).padStart(2, "0");
@@ -323,112 +305,92 @@ class AppointmentCalendarWidget {
323
305
  const fromDateTimeTs = formatTime(startDate);
324
306
  const toDateTimeTs = formatTime(endDate);
325
307
  const patientAddressPayload = {
326
- addressLine1: this.patientAddress,
327
- city: this.patientCity,
328
- state: this.patientState,
329
- country: this.patientCountry,
330
- zipcode: this.patientZipcode,
331
- landmark: this.patientLandmark || undefined,
308
+ addressLine1: this.state.patientAddress,
309
+ city: this.state.patientCity,
310
+ state: this.state.patientState,
311
+ country: this.state.patientCountry,
312
+ zipcode: this.state.patientZipcode,
313
+ landmark: this.state.patientLandmark || undefined,
332
314
  };
333
315
  await AppointmentService.createAppointment({
334
- workspaceId: this.workspaceId,
335
- workspaceAddressId: this.selectedAddress,
336
- doctorId: this.selectedDoctor,
316
+ workspaceId: this.state.workspaceId,
317
+ workspaceAddressId: this.state.selectedAddress,
318
+ doctorId: this.state.selectedDoctor,
337
319
  mode: "OFFLINE",
338
320
  appointmentDate,
339
321
  fromDateTimeTs,
340
322
  toDateTimeTs,
341
- consultationCharge: this.consultationCharge || "0",
323
+ consultationCharge: this.state.consultationCharge || "0",
342
324
  type: "CONSULTATION",
343
325
  source: "SDK_POWERED_WEBSITE",
344
326
  patientPayload: {
345
327
  firstName,
346
328
  lastName,
347
- email: this.patientEmail || undefined,
348
- countryCode: this.countryCode,
349
- phoneNumber: this.patientPhone,
350
- age: this.patientAge ? parseInt(this.patientAge, 10) : undefined,
351
- gender: this.patientGender
352
- ? this.patientGender.toUpperCase()
329
+ email: this.state.patientEmail || undefined,
330
+ countryCode: this.state.countryCode,
331
+ phoneNumber: this.state.patientPhone,
332
+ age: this.state.patientAge
333
+ ? parseInt(this.state.patientAge, 10)
334
+ : undefined,
335
+ gender: this.state.patientGender
336
+ ? this.state.patientGender.toUpperCase()
353
337
  : undefined,
354
338
  },
355
339
  patientAddress: patientAddressPayload,
356
340
  });
357
- this.step = 5;
341
+ this.state.step = 5;
358
342
  this.options.onSuccess?.();
359
343
  }
360
344
  catch (e) {
361
345
  const msg = e.message || "Failed to create appointment";
362
- this.setError(msg);
346
+ this.setState({ error: msg });
363
347
  this.options.onError?.(e);
364
348
  }
365
349
  finally {
366
- this.setLoading(false);
350
+ this.setState({ loading: false });
367
351
  this.render();
368
352
  }
369
353
  }
370
354
  goToNext() {
371
- if (this.step === 0) {
372
- if (this.addresses.length > 1 && !this.selectedAddress)
355
+ if (this.state.step === 0) {
356
+ if (this.state.addresses.length > 1 && !this.state.selectedAddress)
373
357
  return;
374
- if (this.doctors.length > 1 && !this.selectedDoctor)
358
+ if (this.doctors.length > 1 && !this.state.selectedDoctor)
375
359
  return;
376
- this.step = 1;
360
+ this.state.step = 1;
377
361
  this.render();
378
362
  return;
379
363
  }
380
- if (this.step === 1 && this.date) {
381
- this.step = 2;
364
+ const dateStr = formatDateToISO(this.state.selectedDate);
365
+ if (this.state.step === 1 && dateStr) {
366
+ this.state.step = 2;
382
367
  this.loadSlots();
383
368
  return;
384
369
  }
385
- if (this.step === 2 && this.selectedSlot) {
386
- this.step = 3;
370
+ if (this.state.step === 2 && this.state.selectedSlot) {
371
+ this.state.step = 3;
387
372
  this.render();
388
373
  return;
389
374
  }
390
- if (this.step === 3 && this.otpVerified) {
391
- this.step = 4;
375
+ if (this.state.step === 3 && this.state.otpVerified) {
376
+ this.state.step = 4;
392
377
  this.render();
393
378
  return;
394
379
  }
395
- this.step = Math.min(5, this.step + 1);
380
+ this.state.step = Math.min(5, this.state.step + 1);
396
381
  this.render();
397
382
  }
398
383
  goBack() {
399
- this.step = Math.max(0, this.step - 1);
384
+ this.state.step = Math.max(0, this.state.step - 1);
400
385
  this.render();
401
386
  }
402
387
  reset() {
403
- this.step = 0;
404
- this.selectedSlot = null;
405
- this.selectedDoctor = null;
406
- this.date = "";
407
- this.patientName = "";
408
- this.patientAge = "";
409
- this.patientAddress = "";
410
- this.patientCity = "";
411
- this.patientState = "";
412
- this.patientCountry = "";
413
- this.patientZipcode = "";
414
- this.patientLandmark = "";
415
- this.patientEmail = "";
416
- this.patientGender = "";
417
- this.problemFacing = "";
418
- this.consultationCharge = "";
419
- this.countryCode = "+91";
420
- this.patientPhone = "";
421
- this.otpCode = "";
422
- this.otpSent = false;
423
- this.otpVerified = false;
424
- this.slots = [];
388
+ this.state = { ...INITIAL_STATE };
389
+ this.doctors = [];
425
390
  this.render();
426
391
  }
427
- setLoading(loading) {
428
- this.loading = loading;
429
- }
430
- setError(error) {
431
- this.error = error;
392
+ setState(updates) {
393
+ this.state = { ...this.state, ...updates };
432
394
  }
433
395
  render() {
434
396
  if (!this.mounted)
@@ -439,16 +401,20 @@ class AppointmentCalendarWidget {
439
401
  <div class="medos-appointment-header">
440
402
  <h2 class="medos-appointment-title">Book Appointment</h2>
441
403
  <div class="medos-appointment-stepper">
442
- <div class="medos-appointment-step-pill ${this.step === 0 ? "active" : ""}">1 Address</div>
443
- <div class="medos-appointment-step-pill ${this.step === 1 ? "active" : ""}">2 Date</div>
444
- <div class="medos-appointment-step-pill ${this.step === 2 ? "active" : ""}">3 Slot</div>
445
- <div class="medos-appointment-step-pill ${this.step === 3 ? "active" : ""}">4 Phone</div>
446
- <div class="medos-appointment-step-pill ${this.step === 4 ? "active" : ""}">5 Details</div>
404
+ <div class="medos-appointment-step-pill ${this.state.step === 0 ? "active" : ""}">1 Address</div>
405
+ <div class="medos-appointment-step-pill ${this.state.step === 1 ? "active" : ""}">2 Date</div>
406
+ <div class="medos-appointment-step-pill ${this.state.step === 2 ? "active" : ""}">3 Slot</div>
407
+ <div class="medos-appointment-step-pill ${this.state.step === 3 ? "active" : ""}">4 Phone</div>
408
+ <div class="medos-appointment-step-pill ${this.state.step === 4 ? "active" : ""}">5 Details</div>
447
409
  </div>
448
410
  </div>
449
411
 
450
- ${this.loading ? '<div class="medos-appointment-loading">Loading...</div>' : ""}
451
- ${this.error ? `<div class="medos-appointment-error">${this.escapeHtml(this.error)}</div>` : ""}
412
+ ${this.state.loading
413
+ ? '<div class="medos-appointment-loading">Loading...</div>'
414
+ : ""}
415
+ ${this.state.error
416
+ ? `<div class="medos-appointment-error">${this.escapeHtml(this.state.error)}</div>`
417
+ : ""}
452
418
 
453
419
  ${this.renderStep()}
454
420
  </div>
@@ -457,7 +423,7 @@ class AppointmentCalendarWidget {
457
423
  this.attachEventListeners();
458
424
  }
459
425
  renderStep() {
460
- switch (this.step) {
426
+ switch (this.state.step) {
461
427
  case 0:
462
428
  return this.renderStep0();
463
429
  case 1:
@@ -481,14 +447,16 @@ class AppointmentCalendarWidget {
481
447
  <div class="medos-appointment-form-grid-2col">
482
448
  <div>
483
449
  <label class="medos-appointment-label">Address</label>
484
- ${this.addresses.length === 0
450
+ ${this.state.addresses.length === 0
485
451
  ? '<div class="medos-appointment-small-muted">No addresses available</div>'
486
- : this.addresses.length === 1
487
- ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.addresses[0].label)}</div>`
452
+ : this.state.addresses.length === 1
453
+ ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.state.addresses[0].label || "")}</div>`
488
454
  : `
489
455
  <select class="medos-appointment-select" id="medos-address-select">
490
456
  <option value="">-- choose address --</option>
491
- ${this.addresses.map(a => `<option value="${this.escapeHtml(a.id)}" ${this.selectedAddress === a.id ? "selected" : ""}>${this.escapeHtml(a.label)}</option>`).join("")}
457
+ ${this.state.addresses
458
+ .map((a) => `<option value="${this.escapeHtml(a.id.toString())}" ${this.state.selectedAddress === a.id ? "selected" : ""}>${this.escapeHtml(a.label || "")}</option>`)
459
+ .join("")}
492
460
  </select>
493
461
  `}
494
462
  </div>
@@ -497,11 +465,17 @@ class AppointmentCalendarWidget {
497
465
  ${this.doctors.length === 0
498
466
  ? '<div class="medos-appointment-small-muted">No doctors available</div>'
499
467
  : this.doctors.length === 1
500
- ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty ? ` • ${this.escapeHtml(this.doctors[0].specialty)}` : ""}</div>`
468
+ ? `<div class="medos-appointment-small-muted" style="font-weight: 600">${this.escapeHtml(this.doctors[0].name)}${this.doctors[0].specialty
469
+ ? ` • ${this.escapeHtml(this.doctors[0].specialty)}`
470
+ : ""}</div>`
501
471
  : `
502
472
  <select class="medos-appointment-select" id="medos-doctor-select">
503
473
  <option value="">-- choose doctor --</option>
504
- ${this.doctors.map(d => `<option value="${this.escapeHtml(d.id)}" ${this.selectedDoctor === d.id ? "selected" : ""}>${this.escapeHtml(d.name)}${d.specialty ? ` (${this.escapeHtml(d.specialty)})` : ""}</option>`).join("")}
474
+ ${this.doctors
475
+ .map((d) => `<option value="${this.escapeHtml(d.id.toString())}" ${this.state.selectedDoctor === d.id ? "selected" : ""}>${this.escapeHtml(d.name)}${d.specialty
476
+ ? ` (${this.escapeHtml(d.specialty)})`
477
+ : ""}</option>`)
478
+ .join("")}
505
479
  </select>
506
480
  `}
507
481
  </div>
@@ -514,13 +488,14 @@ class AppointmentCalendarWidget {
514
488
  `;
515
489
  }
516
490
  renderStep1() {
491
+ const dateStr = formatDateToISO(this.state.selectedDate);
517
492
  return `
518
493
  <div class="medos-appointment-section">
519
494
  <label class="medos-appointment-label">Select Date</label>
520
- <input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(this.date)}" />
495
+ <input type="date" class="medos-appointment-input" id="medos-date-input" value="${this.escapeHtml(dateStr)}" />
521
496
  <div class="medos-appointment-actions">
522
497
  <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
523
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!this.date ? "disabled" : ""} style="opacity: ${this.date ? 1 : 0.6}">Next</button>
498
+ <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!dateStr ? "disabled" : ""} style="opacity: ${dateStr ? 1 : 0.6}">Next</button>
524
499
  </div>
525
500
  </div>
526
501
  `;
@@ -529,50 +504,63 @@ class AppointmentCalendarWidget {
529
504
  return `
530
505
  <div class="medos-appointment-section">
531
506
  <label class="medos-appointment-label">Choose Time Slot</label>
532
- ${this.slots.length === 0
507
+ ${this.state.slots.length === 0
533
508
  ? '<div class="medos-appointment-small-muted">No slots available for selected date</div>'
534
509
  : `
535
510
  <div class="medos-appointment-slot-grid">
536
- ${this.slots.map(s => {
537
- const start = new Date(s.start).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
538
- const end = new Date(s.end).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
539
- const selected = this.selectedSlot?.start === s.start && this.selectedSlot?.end === s.end;
511
+ ${this.state.slots
512
+ .map((s) => {
513
+ const start = new Date(s.start).toLocaleTimeString([], {
514
+ hour: "2-digit",
515
+ minute: "2-digit",
516
+ });
517
+ const end = new Date(s.end).toLocaleTimeString([], {
518
+ hour: "2-digit",
519
+ minute: "2-digit",
520
+ });
521
+ const selected = this.state.selectedSlot?.start === s.start &&
522
+ this.state.selectedSlot?.end === s.end;
540
523
  return `
541
524
  <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)}">
542
525
  <div class="medos-appointment-slot-time">${start} — ${end}</div>
543
526
  </div>
544
527
  `;
545
- }).join("")}
528
+ })
529
+ .join("")}
546
530
  </div>
547
531
  `}
548
532
  <div class="medos-appointment-actions">
549
533
  <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
550
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-next" ${!this.selectedSlot ? "disabled" : ""} style="opacity: ${this.selectedSlot ? 1 : 0.6}">Next</button>
534
+ <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>
551
535
  </div>
552
536
  </div>
553
537
  `;
554
538
  }
555
539
  renderStep3() {
556
- const countryCodeValid = this.countryCode && this.validateCountryCode(this.countryCode);
557
- const phoneValid = this.patientPhone && this.validatePhoneNumber(this.patientPhone);
558
- const canSendOtp = countryCodeValid && phoneValid && !this.otpSending;
559
- if (!this.otpSent) {
540
+ const countryCodeValid = this.state.countryCode && validateCountryCode(this.state.countryCode);
541
+ const phoneValid = this.state.patientPhone && validatePhoneNumber(this.state.patientPhone);
542
+ const canSendOtp = countryCodeValid && phoneValid && !this.state.otpSending;
543
+ if (!this.state.otpSent) {
560
544
  return `
561
545
  <div class="medos-appointment-section">
562
546
  <label class="medos-appointment-label">Country Code</label>
563
- <input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.countryCode)}" />
564
- ${this.countryCode && !countryCodeValid ? '<div class="medos-appointment-validation-error">Please enter a valid country code (e.g., +91, +1)</div>' : ""}
547
+ <input type="text" class="medos-appointment-input" id="medos-country-code" placeholder="+91" value="${this.escapeHtml(this.state.countryCode)}" />
548
+ ${this.state.countryCode && !countryCodeValid
549
+ ? '<div class="medos-appointment-validation-error">Please enter a valid country code (e.g., +91, +1)</div>'
550
+ : ""}
565
551
  <label class="medos-appointment-label" style="margin-top: 12px">Phone Number</label>
566
- <input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.patientPhone)}" maxlength="15" />
567
- ${this.patientPhone && !phoneValid ? '<div class="medos-appointment-validation-error">Phone number should be 7-15 digits</div>' : ""}
552
+ <input type="tel" class="medos-appointment-input" id="medos-phone" placeholder="9311840587" value="${this.escapeHtml(this.state.patientPhone)}" maxlength="15" />
553
+ ${this.state.patientPhone && !phoneValid
554
+ ? '<div class="medos-appointment-validation-error">Phone number should be 7-15 digits</div>'
555
+ : ""}
568
556
  <div class="medos-appointment-actions">
569
557
  <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
570
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-send-otp" ${!canSendOtp ? "disabled" : ""} style="opacity: ${canSendOtp ? 1 : 0.6}">${this.otpSending ? "Sending..." : "Send OTP"}</button>
558
+ <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>
571
559
  </div>
572
560
  </div>
573
561
  `;
574
562
  }
575
- if (this.otpVerified) {
563
+ if (this.state.otpVerified) {
576
564
  return `
577
565
  <div class="medos-appointment-section">
578
566
  <div class="medos-appointment-verified">✓ Phone verified successfully</div>
@@ -586,61 +574,69 @@ class AppointmentCalendarWidget {
586
574
  return `
587
575
  <div class="medos-appointment-section">
588
576
  <label class="medos-appointment-label">Enter OTP</label>
589
- <input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.otpCode)}" maxlength="6" />
590
- <div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.countryCode)} ${this.escapeHtml(this.patientPhone)}</div>
577
+ <input type="text" class="medos-appointment-input" id="medos-otp" placeholder="Enter 6-digit OTP" value="${this.escapeHtml(this.state.otpCode)}" maxlength="6" />
578
+ <div class="medos-appointment-otp-info">OTP sent to ${this.escapeHtml(this.state.countryCode)} ${this.escapeHtml(this.state.patientPhone)}</div>
591
579
  <div class="medos-appointment-actions">
592
580
  <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-change-number">Change Number</button>
593
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.otpCode.length !== 6 || this.otpVerifying ? "disabled" : ""} style="opacity: ${this.otpCode.length === 6 && !this.otpVerifying ? 1 : 0.6}">${this.otpVerifying ? "Verifying..." : "Verify OTP"}</button>
581
+ <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-verify-otp" ${this.state.otpCode.length !== 6 || this.state.otpVerifying
582
+ ? "disabled"
583
+ : ""} style="opacity: ${this.state.otpCode.length === 6 && !this.state.otpVerifying ? 1 : 0.6}">${this.state.otpVerifying ? "Verifying..." : "Verify OTP"}</button>
594
584
  </div>
595
585
  </div>
596
586
  `;
597
587
  }
598
588
  renderStep4() {
599
- const canSubmit = this.patientName && this.patientAddress && this.patientCity && this.patientState && this.patientCountry && this.patientZipcode && this.otpVerified;
589
+ const canSubmit = this.state.patientName &&
590
+ this.state.patientAddress &&
591
+ this.state.patientCity &&
592
+ this.state.patientState &&
593
+ this.state.patientCountry &&
594
+ this.state.patientZipcode &&
595
+ this.state.otpVerified;
600
596
  return `
601
597
  <div class="medos-appointment-section">
602
598
  <label class="medos-appointment-label">Patient Name</label>
603
- <input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.patientName)}" />
599
+ <input type="text" class="medos-appointment-input" id="medos-patient-name" placeholder="Full name" value="${this.escapeHtml(this.state.patientName)}" />
604
600
  <label class="medos-appointment-label" style="margin-top: 12px">Age</label>
605
- <input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.patientAge)}" />
601
+ <input type="number" class="medos-appointment-input" id="medos-patient-age" placeholder="Age" value="${this.escapeHtml(this.state.patientAge)}" />
606
602
  <label class="medos-appointment-label" style="margin-top: 12px">Email (Optional)</label>
607
- <input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.patientEmail)}" />
603
+ <input type="email" class="medos-appointment-input" id="medos-patient-email" placeholder="patient@example.com" value="${this.escapeHtml(this.state.patientEmail)}" />
608
604
  <label class="medos-appointment-label" style="margin-top: 12px">Gender (Optional)</label>
609
605
  <select class="medos-appointment-select" id="medos-patient-gender">
610
606
  <option value="">-- Select Gender --</option>
611
- <option value="MALE" ${this.patientGender === "MALE" ? "selected" : ""}>Male</option>
612
- <option value="FEMALE" ${this.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
613
- <option value="OTHER" ${this.patientGender === "OTHER" ? "selected" : ""}>Other</option>
607
+ <option value="MALE" ${this.state.patientGender === "MALE" ? "selected" : ""}>Male</option>
608
+ <option value="FEMALE" ${this.state.patientGender === "FEMALE" ? "selected" : ""}>Female</option>
609
+ <option value="OTHER" ${this.state.patientGender === "OTHER" ? "selected" : ""}>Other</option>
614
610
  </select>
615
611
  <label class="medos-appointment-label" style="margin-top: 12px">Address Line 1 *</label>
616
- <input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.patientAddress)}" />
612
+ <input type="text" class="medos-appointment-input" id="medos-patient-address" placeholder="Street address, building name, etc." value="${this.escapeHtml(this.state.patientAddress)}" />
617
613
  <div class="medos-appointment-form-grid">
618
614
  <div>
619
615
  <label class="medos-appointment-label">City *</label>
620
- <input type="text" class="medos-appointment-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.patientCity)}" />
616
+ <input type="text" class="medos-appointment-input" id="medos-patient-city" placeholder="City" value="${this.escapeHtml(this.state.patientCity)}" />
621
617
  </div>
622
618
  <div>
623
619
  <label class="medos-appointment-label">State *</label>
624
- <input type="text" class="medos-appointment-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.patientState)}" />
620
+ <input type="text" class="medos-appointment-input" id="medos-patient-state" placeholder="State" value="${this.escapeHtml(this.state.patientState)}" />
625
621
  </div>
626
622
  </div>
627
623
  <div class="medos-appointment-form-grid">
628
624
  <div>
629
625
  <label class="medos-appointment-label">Country *</label>
630
- <input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.patientCountry)}" />
626
+ <input type="text" class="medos-appointment-input" id="medos-patient-country" placeholder="Country" value="${this.escapeHtml(this.state.patientCountry)}" />
631
627
  </div>
632
628
  <div>
633
629
  <label class="medos-appointment-label">Zipcode *</label>
634
- <input type="text" class="medos-appointment-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.patientZipcode)}" />
630
+ <input type="text" class="medos-appointment-input" id="medos-patient-zipcode" placeholder="Zipcode" value="${this.escapeHtml(this.state.patientZipcode)}" />
635
631
  </div>
636
632
  </div>
637
633
  <label class="medos-appointment-label" style="margin-top: 12px">Landmark (Optional)</label>
638
- <input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.patientLandmark)}" />
634
+ <input type="text" class="medos-appointment-input" id="medos-patient-landmark" placeholder="Nearby landmark" value="${this.escapeHtml(this.state.patientLandmark)}" />
639
635
  <label class="medos-appointment-label" style="margin-top: 12px">Problem Facing</label>
640
- <textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.problemFacing)}</textarea>
636
+ <textarea class="medos-appointment-textarea" id="medos-problem-facing" placeholder="Describe the problem you're facing">${this.escapeHtml(this.state.patientName)}</textarea>
641
637
  <div class="medos-appointment-actions">
642
638
  <button class="medos-appointment-btn medos-appointment-btn-secondary" id="medos-btn-back">Back</button>
643
- <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-submit" ${!canSubmit || this.loading ? "disabled" : ""} style="opacity: ${canSubmit && !this.loading ? 1 : 0.6}">${this.loading ? "Booking..." : "Book Appointment"}</button>
639
+ <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>
644
640
  </div>
645
641
  </div>
646
642
  `;
@@ -651,7 +647,7 @@ class AppointmentCalendarWidget {
651
647
  <div class="medos-appointment-success-card">
652
648
  <div class="medos-appointment-success-icon">✓</div>
653
649
  <div class="medos-appointment-success-title">Appointment Confirmed</div>
654
- <div class="medos-appointment-small-muted">Thank you, ${this.escapeHtml(this.patientName || "Patient")}. Your appointment is confirmed.</div>
650
+ <div class="medos-appointment-small-muted">Thank you, ${this.escapeHtml(this.state.patientName || "Patient")}. Your appointment is confirmed.</div>
655
651
  </div>
656
652
  <div style="margin-top: 14px; display: flex; justify-content: center">
657
653
  <button class="medos-appointment-btn medos-appointment-btn-primary" id="medos-btn-book-another" style="width: 160px">Book Another</button>
@@ -664,14 +660,14 @@ class AppointmentCalendarWidget {
664
660
  if (addressSelect) {
665
661
  addressSelect.addEventListener("change", (e) => {
666
662
  const target = e.target;
667
- this.handleAddressChange(target.value || null);
663
+ this.handleAddressChange(Number(target.value) || null);
668
664
  });
669
665
  }
670
666
  const doctorSelect = this.container.querySelector("#medos-doctor-select");
671
667
  if (doctorSelect) {
672
668
  doctorSelect.addEventListener("change", (e) => {
673
669
  const target = e.target;
674
- this.selectedDoctor = target.value || null;
670
+ this.state.selectedDoctor = Number(target.value) || null;
675
671
  this.render();
676
672
  });
677
673
  }
@@ -679,18 +675,22 @@ class AppointmentCalendarWidget {
679
675
  if (dateInput) {
680
676
  dateInput.addEventListener("change", (e) => {
681
677
  const target = e.target;
682
- this.date = target.value;
678
+ this.state.selectedDate = new Date(target.value);
683
679
  this.render();
684
680
  });
685
681
  }
686
682
  const slotCards = this.container.querySelectorAll(".medos-appointment-slot-card");
687
- slotCards.forEach(card => {
683
+ slotCards.forEach((card) => {
688
684
  card.addEventListener("click", () => {
689
685
  const slotId = card.getAttribute("data-slot-id");
690
686
  const slotStart = card.getAttribute("data-slot-start");
691
687
  const slotEnd = card.getAttribute("data-slot-end");
692
688
  if (slotStart && slotEnd) {
693
- this.selectedSlot = { start: slotStart, end: slotEnd, id: slotId || undefined };
689
+ this.state.selectedSlot = {
690
+ start: slotStart,
691
+ end: slotEnd,
692
+ id: slotId || undefined,
693
+ };
694
694
  this.render();
695
695
  }
696
696
  });
@@ -704,7 +704,7 @@ class AppointmentCalendarWidget {
704
704
  value = "+" + value;
705
705
  }
706
706
  value = value.replace(/[^\d+]/g, "");
707
- this.countryCode = value;
707
+ this.state.countryCode = value;
708
708
  target.value = value;
709
709
  this.render();
710
710
  });
@@ -713,8 +713,8 @@ class AppointmentCalendarWidget {
713
713
  if (phoneInput) {
714
714
  phoneInput.addEventListener("input", (e) => {
715
715
  const target = e.target;
716
- this.patientPhone = target.value.replace(/\D/g, "");
717
- target.value = this.patientPhone;
716
+ this.state.patientPhone = target.value.replace(/\D/g, "");
717
+ target.value = this.state.patientPhone;
718
718
  this.render();
719
719
  });
720
720
  }
@@ -722,7 +722,7 @@ class AppointmentCalendarWidget {
722
722
  if (otpInput) {
723
723
  otpInput.addEventListener("input", (e) => {
724
724
  const target = e.target;
725
- this.otpCode = target.value;
725
+ this.state.otpCode = target.value;
726
726
  this.render();
727
727
  });
728
728
  }
@@ -730,77 +730,70 @@ class AppointmentCalendarWidget {
730
730
  if (patientNameInput) {
731
731
  patientNameInput.addEventListener("input", (e) => {
732
732
  const target = e.target;
733
- this.patientName = target.value;
733
+ this.state.patientName = target.value;
734
734
  });
735
735
  }
736
736
  const patientAgeInput = this.container.querySelector("#medos-patient-age");
737
737
  if (patientAgeInput) {
738
738
  patientAgeInput.addEventListener("input", (e) => {
739
739
  const target = e.target;
740
- this.patientAge = target.value;
740
+ this.state.patientAge = target.value;
741
741
  });
742
742
  }
743
743
  const patientEmailInput = this.container.querySelector("#medos-patient-email");
744
744
  if (patientEmailInput) {
745
745
  patientEmailInput.addEventListener("input", (e) => {
746
746
  const target = e.target;
747
- this.patientEmail = target.value;
747
+ this.state.patientEmail = target.value;
748
748
  });
749
749
  }
750
750
  const patientGenderSelect = this.container.querySelector("#medos-patient-gender");
751
751
  if (patientGenderSelect) {
752
752
  patientGenderSelect.addEventListener("change", (e) => {
753
753
  const target = e.target;
754
- this.patientGender = target.value;
754
+ this.state.patientGender = target.value;
755
755
  });
756
756
  }
757
757
  const patientAddressInput = this.container.querySelector("#medos-patient-address");
758
758
  if (patientAddressInput) {
759
759
  patientAddressInput.addEventListener("input", (e) => {
760
760
  const target = e.target;
761
- this.patientAddress = target.value;
761
+ this.state.patientAddress = target.value;
762
762
  });
763
763
  }
764
764
  const patientCityInput = this.container.querySelector("#medos-patient-city");
765
765
  if (patientCityInput) {
766
766
  patientCityInput.addEventListener("input", (e) => {
767
767
  const target = e.target;
768
- this.patientCity = target.value;
768
+ this.state.patientCity = target.value;
769
769
  });
770
770
  }
771
771
  const patientStateInput = this.container.querySelector("#medos-patient-state");
772
772
  if (patientStateInput) {
773
773
  patientStateInput.addEventListener("input", (e) => {
774
774
  const target = e.target;
775
- this.patientState = target.value;
775
+ this.state.patientState = target.value;
776
776
  });
777
777
  }
778
778
  const patientCountryInput = this.container.querySelector("#medos-patient-country");
779
779
  if (patientCountryInput) {
780
780
  patientCountryInput.addEventListener("input", (e) => {
781
781
  const target = e.target;
782
- this.patientCountry = target.value;
782
+ this.state.patientCountry = target.value;
783
783
  });
784
784
  }
785
785
  const patientZipcodeInput = this.container.querySelector("#medos-patient-zipcode");
786
786
  if (patientZipcodeInput) {
787
787
  patientZipcodeInput.addEventListener("input", (e) => {
788
788
  const target = e.target;
789
- this.patientZipcode = target.value;
789
+ this.state.patientZipcode = target.value;
790
790
  });
791
791
  }
792
792
  const patientLandmarkInput = this.container.querySelector("#medos-patient-landmark");
793
793
  if (patientLandmarkInput) {
794
794
  patientLandmarkInput.addEventListener("input", (e) => {
795
795
  const target = e.target;
796
- this.patientLandmark = target.value;
797
- });
798
- }
799
- const problemFacingInput = this.container.querySelector("#medos-problem-facing");
800
- if (problemFacingInput) {
801
- problemFacingInput.addEventListener("input", (e) => {
802
- const target = e.target;
803
- this.problemFacing = target.value;
796
+ this.state.patientLandmark = target.value;
804
797
  });
805
798
  }
806
799
  const nextBtn = this.container.querySelector("#medos-btn-next");
@@ -822,9 +815,7 @@ class AppointmentCalendarWidget {
822
815
  const changeNumberBtn = this.container.querySelector("#medos-btn-change-number");
823
816
  if (changeNumberBtn) {
824
817
  changeNumberBtn.addEventListener("click", () => {
825
- this.otpSent = false;
826
- this.otpCode = "";
827
- this.otpVerified = false;
818
+ this.setState({ otpSent: false, otpCode: "", otpVerified: false });
828
819
  this.render();
829
820
  });
830
821
  }