medos-sdk 1.0.2 → 1.1.0

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